Merge "Add NR SA OVERRIDE_NETWORK_TYPE_NR_ADVANCED for TelephonyDisplayInfo" into sc-dev
diff --git a/Android.bp b/Android.bp
index 9655daf..7e68986 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,7 @@
         ":framework_native_aidl",
         ":gatekeeper_aidl",
         ":gsiservice_aidl",
+        ":idmap2_core_aidl",
         ":incidentcompanion_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
@@ -405,6 +406,7 @@
         ":framework-statsd-sources",
         ":framework-tethering-srcs",
         ":framework-wifi-updatable-sources",
+        ":ike-srcs",
         ":updatable-media-srcs",
     ],
     visibility: ["//visibility:private"],
@@ -413,6 +415,7 @@
 java_library {
     name: "framework-updatable-stubs-module_libs_api",
     static_libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
         "framework-appsearch.stubs.module_lib",
         "framework-graphics.stubs.module_lib",
         "framework-media.stubs.module_lib",
@@ -432,6 +435,7 @@
     name: "framework-all",
     installable: false,
     static_libs: [
+        "android.net.ipsec.ike.impl",
         "framework-minus-apex",
         "framework-appsearch.impl",
         "framework-graphics.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 86364af..3f2e898 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -306,6 +306,7 @@
     name: "android_stubs_current",
     srcs: [ ":api-stubs-docs-non-updatable" ],
     static_libs: [
+        "android.net.ipsec.ike.stubs",
         "art.module.public.api.stubs",
         "conscrypt.module.public.api.stubs",
         "framework-appsearch.stubs",
@@ -328,6 +329,7 @@
     name: "android_system_stubs_current",
     srcs: [ ":system-api-stubs-docs-non-updatable" ],
     static_libs: [
+        "android.net.ipsec.ike.stubs.system",
         "art.module.public.api.stubs",
         "conscrypt.module.public.api.stubs",
         "framework-appsearch.stubs.system",
@@ -366,6 +368,7 @@
     static_libs: [
         // Modules do not have test APIs, but we want to include their SystemApis, like we include
         // the SystemApi of framework-non-updatable-sources.
+        "android.net.ipsec.ike.stubs.system",
         "art.module.public.api.stubs",
         "conscrypt.module.public.api.stubs",
         "framework-appsearch.stubs.system",
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index a3b8013..905000a 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -208,8 +208,8 @@
     ctor public GetByUriRequest.Builder();
     method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
     method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
-    method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.lang.String...);
-    method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>);
+    method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.lang.String...);
+    method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.GetByUriRequest build();
     method @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
   }
@@ -243,8 +243,8 @@
 
   public static final class RemoveByUriRequest.Builder {
     ctor public RemoveByUriRequest.Builder();
-    method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUri(@NonNull java.lang.String...);
-    method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>);
+    method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.lang.String...);
+    method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.RemoveByUriRequest build();
     method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String);
   }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 0fcf061..656608d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -122,14 +122,14 @@
 
         /** Adds one or more URIs to the request. */
         @NonNull
-        public Builder addUri(@NonNull String... uris) {
+        public Builder addUris(@NonNull String... uris) {
             Preconditions.checkNotNull(uris);
-            return addUri(Arrays.asList(uris));
+            return addUris(Arrays.asList(uris));
         }
 
         /** Adds one or more URIs to the request. */
         @NonNull
-        public Builder addUri(@NonNull Collection<String> uris) {
+        public Builder addUris(@NonNull Collection<String> uris) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkNotNull(uris);
             mUris.addAll(uris);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index 2104198d..198eee8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -73,14 +73,14 @@
 
         /** Adds one or more URIs to the request. */
         @NonNull
-        public Builder addUri(@NonNull String... uris) {
+        public Builder addUris(@NonNull String... uris) {
             Preconditions.checkNotNull(uris);
-            return addUri(Arrays.asList(uris));
+            return addUris(Arrays.asList(uris));
         }
 
         /** Adds one or more URIs to the request. */
         @NonNull
-        public Builder addUri(@NonNull Collection<String> uris) {
+        public Builder addUris(@NonNull Collection<String> uris) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkNotNull(uris);
             mUris.addAll(uris);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index 0328d54..4869aa3 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -163,7 +163,7 @@
 
         /** Adds deletedTypes to the list of deleted schema types. */
         @NonNull
-        public Builder addDeletedType(@NonNull Collection<String> deletedTypes) {
+        public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes));
             return this;
@@ -171,7 +171,7 @@
 
         /** Adds incompatibleTypes to the list of incompatible schema types. */
         @NonNull
-        public Builder addIncompatibleType(@NonNull Collection<String> incompatibleTypes) {
+        public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes));
             return this;
@@ -179,7 +179,7 @@
 
         /** Adds migratedTypes to the list of migrated schema types. */
         @NonNull
-        public Builder addMigratedType(@NonNull Collection<String> migratedTypes) {
+        public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
             return this;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java
index 1b4d284..14dd472 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java
@@ -147,35 +147,41 @@
         if (bundle == null) {
             return 0;
         }
-        int[] hashCodes = new int[bundle.size()];
-        int i = 0;
+        int[] hashCodes = new int[bundle.size() + 1];
+        int hashCodeIdx = 0;
         // Bundle inherit its hashCode() from Object.java, which only relative to their memory
         // address. Bundle doesn't have an order, so we should iterate all keys and combine
         // their value's hashcode into an array. And use the hashcode of the array to be
         // the hashcode of the bundle.
-        for (String key : bundle.keySet()) {
-            Object value = bundle.get(key);
+        // Because bundle.keySet() doesn't guarantee any particular order, we need to sort the keys
+        // in case the iteration order varies from run to run.
+        String[] keys = bundle.keySet().toArray(new String[0]);
+        Arrays.sort(keys);
+        // Hash the keys so we can detect key-only differences
+        hashCodes[hashCodeIdx++] = Arrays.hashCode(keys);
+        for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) {
+            Object value = bundle.get(keys[keyIdx]);
             if (value instanceof Bundle) {
-                hashCodes[i++] = deepHashCode((Bundle) value);
+                hashCodes[hashCodeIdx++] = deepHashCode((Bundle) value);
             } else if (value instanceof int[]) {
-                hashCodes[i++] = Arrays.hashCode((int[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((int[]) value);
             } else if (value instanceof byte[]) {
-                hashCodes[i++] = Arrays.hashCode((byte[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((byte[]) value);
             } else if (value instanceof char[]) {
-                hashCodes[i++] = Arrays.hashCode((char[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((char[]) value);
             } else if (value instanceof long[]) {
-                hashCodes[i++] = Arrays.hashCode((long[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((long[]) value);
             } else if (value instanceof float[]) {
-                hashCodes[i++] = Arrays.hashCode((float[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((float[]) value);
             } else if (value instanceof short[]) {
-                hashCodes[i++] = Arrays.hashCode((short[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((short[]) value);
             } else if (value instanceof double[]) {
-                hashCodes[i++] = Arrays.hashCode((double[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((double[]) value);
             } else if (value instanceof boolean[]) {
-                hashCodes[i++] = Arrays.hashCode((boolean[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((boolean[]) value);
             } else if (value instanceof String[]) {
                 // Optimization to avoid Object[] handler creating an inner array for common cases
-                hashCodes[i++] = Arrays.hashCode((String[]) value);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode((String[]) value);
             } else if (value instanceof Object[]) {
                 Object[] array = (Object[]) value;
                 int[] innerHashCodes = new int[array.length];
@@ -186,7 +192,7 @@
                         innerHashCodes[j] = array[j].hashCode();
                     }
                 }
-                hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
             } else if (value instanceof ArrayList) {
                 ArrayList<?> list = (ArrayList<?>) value;
                 int[] innerHashCodes = new int[list.size()];
@@ -198,7 +204,7 @@
                         innerHashCodes[j] = item.hashCode();
                     }
                 }
-                hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
             } else if (value instanceof SparseArray) {
                 SparseArray<?> array = (SparseArray<?>) value;
                 int[] innerHashCodes = new int[array.size() * 2];
@@ -211,9 +217,9 @@
                         innerHashCodes[j * 2 + 1] = item.hashCode();
                     }
                 }
-                hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+                hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
             } else {
-                hashCodes[i++] = value.hashCode();
+                hashCodes[hashCodeIdx++] = value.hashCode();
             }
         }
         return Arrays.hashCode(hashCodes);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 271129b..c369801 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -108,7 +108,7 @@
                         schemasPackageAccessibleBundles.entrySet()) {
                     List<PackageIdentifier> packageIdentifiers =
                             new ArrayList<>(entry.getValue().size());
-                    for (int i = 0; i < packageIdentifiers.size(); i++) {
+                    for (int i = 0; i < entry.getValue().size(); i++) {
                         packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
                     }
                     schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 8bff720..2f1817e 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -317,7 +317,7 @@
             }
 
             Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible =
-                    new ArrayMap<>(schemasNotPlatformSurfaceable.size());
+                    new ArrayMap<>(schemasPackageAccessible.size());
             for (Map.Entry<String, List<PackageIdentifier>> entry :
                     schemasPackageAccessible.entrySet()) {
                 prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue());
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 12699b7..6ba5572 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-Ibe06fb9c574c8718191f833bb042fa10c300e4e2
+I2bf8bd9db1b71b7da4ab50dd7480e4529678413a
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index eb1623e..6595d8d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -37,8 +37,9 @@
 import java.util.concurrent.Executors;
 
 /**
- * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
- * a consistent interface.
+ * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
+ * consistent interface.
+ *
  * @hide
  */
 public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@@ -47,7 +48,13 @@
 
     @NonNull
     public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
-        Context context = ApplicationProvider.getApplicationContext();
+        return createGlobalSearchSession(ApplicationProvider.getApplicationContext());
+    }
+
+    /** Only for use when called from a non-instrumented context. */
+    @NonNull
+    public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession(
+            @NonNull Context context) {
         AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
         SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
         ExecutorService executor = Executors.newCachedThreadPool();
@@ -62,7 +69,6 @@
             @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
         mGlobalSearchSession = Preconditions.checkNotNull(session);
         mExecutor = Preconditions.checkNotNull(executor);
-
     }
 
     @NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index 20fb909..44d5180 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -59,7 +59,7 @@
                         session.getByUri(
                                 new GetByUriRequest.Builder()
                                         .setNamespace(namespace)
-                                        .addUri(uris)
+                                        .addUris(uris)
                                         .build()));
         assertThat(result.getSuccesses()).hasSize(uris.length);
         assertThat(result.getFailures()).isEmpty();
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 bfc153f..71fe55f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -212,7 +212,7 @@
     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
     final JobConcurrencyManager mConcurrencyManager;
 
-    static final int MSG_JOB_EXPIRED = 0;
+    static final int MSG_CHECK_INDIVIDUAL_JOB = 0;
     static final int MSG_CHECK_JOB = 1;
     static final int MSG_STOP_JOB = 2;
     static final int MSG_CHECK_JOB_GREEDY = 3;
@@ -1711,6 +1711,12 @@
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
+            JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+            if (newJs != null) {
+                // This job was stopped because the app scheduled a new job with the same job ID.
+                // Check if the new job is ready to run.
+                mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget();
+            }
             return;
         }
 
@@ -1748,7 +1754,11 @@
 
     @Override
     public void onRunJobNow(JobStatus jobStatus) {
-        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
+        if (jobStatus == null) {
+            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+        } else {
+            mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget();
+        }
     }
 
     final private class JobHandler extends Handler {
@@ -1764,18 +1774,15 @@
                     return;
                 }
                 switch (message.what) {
-                    case MSG_JOB_EXPIRED: {
-                        JobStatus runNow = (JobStatus) message.obj;
-                        // runNow can be null, which is a controller's way of indicating that its
-                        // state is such that all ready jobs should be run immediately.
-                        if (runNow != null) {
-                            if (!isCurrentlyActiveLocked(runNow)
-                                    && isReadyToBeExecutedLocked(runNow)) {
-                                mJobPackageTracker.notePending(runNow);
-                                addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+                    case MSG_CHECK_INDIVIDUAL_JOB: {
+                        JobStatus js = (JobStatus) message.obj;
+                        if (js != null) {
+                            if (isReadyToBeExecutedLocked(js)) {
+                                mJobPackageTracker.notePending(js);
+                                addOrderedItem(mPendingJobs, js, sPendingJobComparator);
                             }
                         } else {
-                            queueReadyJobsForExecutionLocked();
+                            Slog.e(TAG, "Given null job to check individually");
                         }
                     } break;
                     case MSG_CHECK_JOB:
@@ -1909,12 +1916,10 @@
         // This method will check and capture all ready jobs, so we don't need to keep any messages
         // in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB_GREEDY);
+        mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB);
         // MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready
         // jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB);
-        // This method will capture all expired jobs that are ready, so there's no need to keep
-        // the _EXPIRED messages in the queue.
-        mHandler.removeMessages(MSG_JOB_EXPIRED);
         if (DEBUG) {
             Slog.d(TAG, "queuing all ready jobs for execution:");
         }
@@ -1990,8 +1995,11 @@
                 }
 
                 final boolean shouldForceBatchJob;
-                // Restricted jobs must always be batched
-                if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
+                if (job.shouldTreatAsExpeditedJob()) {
+                    // Never batch expedited jobs, even for RESTRICTED apps.
+                    shouldForceBatchJob = false;
+                } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
+                    // Restricted jobs must always be batched
                     shouldForceBatchJob = true;
                 } else if (job.getNumFailures() > 0) {
                     shouldForceBatchJob = false;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 50723c7..131a6d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -107,8 +107,6 @@
                         taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
                     }
                 }
-                taskStatus.changedAuthorities = null;
-                taskStatus.changedUris = null;
             }
             taskStatus.changedAuthorities = null;
             taskStatus.changedUris = null;
diff --git a/api/Android.bp b/api/Android.bp
index 69dce97..d5c6bf6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -28,6 +28,7 @@
 genrule {
     name: "frameworks-base-api-current.txt",
     srcs: [
+        ":android.net.ipsec.ike{.public.api.txt}",
         ":art.module.public.api{.public.api.txt}",
         ":conscrypt.module.public.api{.public.api.txt}",
         ":framework-appsearch{.public.api.txt}",
@@ -64,6 +65,7 @@
 genrule {
     name: "frameworks-base-api-current.srcjar",
     srcs: [
+        ":android.net.ipsec.ike{.public.stubs.source}",
         ":api-stubs-docs-non-updatable",
         ":art.module.public.api{.public.stubs.source}",
         ":conscrypt.module.public.api{.public.stubs.source}",
@@ -88,6 +90,7 @@
 genrule {
     name: "frameworks-base-api-removed.txt",
     srcs: [
+        ":android.net.ipsec.ike{.public.removed-api.txt}",
         ":art.module.public.api{.public.removed-api.txt}",
         ":conscrypt.module.public.api{.public.removed-api.txt}",
         ":framework-appsearch{.public.removed-api.txt}",
@@ -123,6 +126,7 @@
 genrule {
     name: "frameworks-base-api-system-current.txt",
     srcs: [
+        ":android.net.ipsec.ike{.system.api.txt}",
         ":framework-appsearch{.system.api.txt}",
         ":framework-graphics{.system.api.txt}",
         ":framework-media{.system.api.txt}",
@@ -156,6 +160,7 @@
 genrule {
     name: "frameworks-base-api-system-removed.txt",
     srcs: [
+        ":android.net.ipsec.ike{.system.removed-api.txt}",
         ":framework-appsearch{.system.removed-api.txt}",
         ":framework-graphics{.system.removed-api.txt}",
         ":framework-media{.system.removed-api.txt}",
@@ -189,6 +194,7 @@
 genrule {
     name: "frameworks-base-api-module-lib-current.txt",
     srcs: [
+        ":android.net.ipsec.ike{.module-lib.api.txt}",
         ":framework-appsearch{.module-lib.api.txt}",
         ":framework-graphics{.module-lib.api.txt}",
         ":framework-media{.module-lib.api.txt}",
@@ -221,6 +227,7 @@
 genrule {
     name: "frameworks-base-api-module-lib-removed.txt",
     srcs: [
+        ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
         ":framework-appsearch{.module-lib.removed-api.txt}",
         ":framework-graphics{.module-lib.removed-api.txt}",
         ":framework-media{.module-lib.removed-api.txt}",
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index e21a6b2..3bad889 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -51,13 +51,18 @@
             static: {
                 enabled: false,
             },
+            static_libs: [
+                "libidmap2_protos",
+            ],
             shared_libs: [
                 "libandroidfw",
                 "libbase",
                 "libcutils",
-                "libutils",
-                "libziparchive",
                 "libidmap2_policies",
+                "libprotobuf-cpp-lite",
+                "libutils",
+                "libz",
+                "libziparchive",
             ],
         },
         host: {
@@ -68,15 +73,30 @@
                 "libandroidfw",
                 "libbase",
                 "libcutils",
-                "libutils",
-                "libziparchive",
                 "libidmap2_policies",
+                "libidmap2_protos",
+                "libprotobuf-cpp-lite",
+                "libutils",
+                "libz",
+                "libziparchive",
             ],
         },
     },
 }
 
 cc_library {
+    name: "libidmap2_protos",
+    srcs: [
+        "libidmap2/proto/*.proto",
+    ],
+    host_supported: true,
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+    },
+}
+
+cc_library {
     name: "libidmap2_policies",
     defaults: [
         "idmap2_defaults",
@@ -88,9 +108,6 @@
             enabled: true,
         },
         android: {
-            static: {
-                enabled: false,
-            },
             shared_libs: [
                 "libandroidfw",
             ],
@@ -119,6 +136,7 @@
     srcs: [
         "tests/BinaryStreamVisitorTests.cpp",
         "tests/CommandLineOptionsTests.cpp",
+        "tests/FabricatedOverlayTests.cpp",
         "tests/FileUtilsTests.cpp",
         "tests/Idmap2BinaryTests.cpp",
         "tests/IdmapTests.cpp",
@@ -130,23 +148,27 @@
         "tests/ResourceUtilsTests.cpp",
         "tests/ResultTests.cpp",
         "tests/XmlParserTests.cpp",
-        "tests/ZipFileTests.cpp",
     ],
     required: [
         "idmap2",
     ],
-    static_libs: ["libgmock"],
+    static_libs: [
+        "libgmock",
+        "libidmap2_protos",
+    ],
     target: {
         android: {
             shared_libs: [
                 "libandroidfw",
                 "libbase",
                 "libidmap2",
+                "libidmap2_policies",
                 "liblog",
+                "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
+                "libz",
                 "libziparchive",
-                "libidmap2_policies",
             ],
         },
         host: {
@@ -155,17 +177,28 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
+                "libidmap2_policies",
                 "liblog",
+                "libprotobuf-cpp-lite",
                 "libutils",
                 "libziparchive",
-                "libidmap2_policies",
             ],
             shared_libs: [
                 "libz",
             ],
+            data: [
+                ":libz",
+                ":idmap2",
+            ],
         },
     },
-    data: ["tests/data/**/*.apk"],
+    data: [
+        "tests/data/**/*.apk",
+    ],
+    compile_multilib: "first",
+    test_options: {
+        unit_test: true,
+    },
 }
 
 cc_binary {
@@ -182,6 +215,9 @@
         "idmap2/Lookup.cpp",
         "idmap2/Main.cpp",
     ],
+    static_libs: [
+        "libidmap2_protos",
+    ],
     target: {
         android: {
             shared_libs: [
@@ -189,9 +225,11 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
-                "libutils",
-                "libziparchive",
                 "libidmap2_policies",
+                "libprotobuf-cpp-lite",
+                "libutils",
+                "libz",
+                "libziparchive",
             ],
         },
         host: {
@@ -200,10 +238,11 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
+                "libidmap2_policies",
                 "liblog",
+                "libprotobuf-cpp-lite",
                 "libutils",
                 "libziparchive",
-                "libidmap2_policies",
             ],
             shared_libs: [
                 "libz",
@@ -229,11 +268,14 @@
         "libbinder",
         "libcutils",
         "libidmap2",
+        "libidmap2_policies",
+        "libprotobuf-cpp-lite",
         "libutils",
         "libziparchive",
-        "libidmap2_policies",
     ],
     static_libs: [
+        "libc++fs",
+        "libidmap2_protos",
         "libidmap2daidl",
     ],
     init_rc: ["idmap2d/idmap2d.rc"],
@@ -241,28 +283,41 @@
 
 cc_library_static {
     name: "libidmap2daidl",
-    defaults: [
-        "idmap2_defaults",
-    ],
-    tidy: false,
-    host_supported: false,
     srcs: [
         ":idmap2_aidl",
+        ":idmap2_core_aidl",
+    ],
+    header_libs: [
+        "libbinder_headers",
     ],
     shared_libs: [
         "libbase",
     ],
     aidl: {
         export_aidl_headers: true,
+        local_include_dirs: [
+            "idmap2d/aidl/core",
+            "idmap2d/aidl/services/",
+        ],
     },
 }
 
 filegroup {
+    name: "idmap2_core_aidl",
+    srcs: [
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl",
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl",
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl",
+    ],
+    path: "idmap2d/aidl/core/",
+}
+
+filegroup {
     name: "idmap2_aidl",
     srcs: [
-        "idmap2d/aidl/android/os/IIdmap2.aidl",
+        "idmap2d/aidl/services/android/os/IIdmap2.aidl",
     ],
-    path: "idmap2d/aidl",
+    path: "idmap2d/aidl/services/",
 }
 
 aidl_interface {
@@ -274,7 +329,7 @@
 filegroup {
     name: "overlayable_policy_aidl_files",
     srcs: [
-        "idmap2d/aidl/android/os/OverlayablePolicy.aidl",
+        "idmap2d/aidl/services/android/os/OverlayablePolicy.aidl",
     ],
-    path: "idmap2d/aidl",
+    path: "idmap2d/aidl/services/",
 }
diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml
deleted file mode 100644
index 5147f4e..0000000
--- a/cmds/idmap2/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for idmap2_tests">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" />
-    </target_preparer>
-    <option name="test-suite-tag" value="idmap2_tests" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="idmap2_tests" />
-    </test>
-</configuration>
diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
index 09867f3..bf30a76 100644
--- a/cmds/idmap2/idmap2/CommandUtils.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -25,7 +25,9 @@
 
 using android::idmap2::Error;
 using android::idmap2::IdmapHeader;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::Unit;
 
 Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
@@ -39,11 +41,20 @@
     return Error("failed to parse idmap header");
   }
 
-  const auto header_ok = header->IsUpToDate(target_path, overlay_path, overlay_name,
-                                            fulfilled_policies, enforce_overlayable);
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return Error("failed to load target '%s'", target_path.c_str());
+  }
+
+  auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return Error("failed to load overlay '%s'", overlay_path.c_str());
+  }
+
+  const auto header_ok = header->IsUpToDate(**target, **overlay, overlay_name, fulfilled_policies,
+                                            enforce_overlayable);
   if (!header_ok) {
     return Error(header_ok.GetError(), "idmap not up to date");
   }
-
   return Unit{};
 }
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index c93c717..977a0bb 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -20,7 +20,6 @@
 #include <fstream>
 #include <memory>
 #include <ostream>
-#include <string>
 #include <vector>
 
 #include "androidfw/ResourceTypes.h"
@@ -31,12 +30,13 @@
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/SysTrace.h"
 
-using android::ApkAssets;
 using android::idmap2::BinaryStreamVisitor;
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::Unit;
 using android::idmap2::utils::kIdmapFilePermissionMask;
 using android::idmap2::utils::PoliciesToBitmaskResult;
@@ -93,18 +93,18 @@
     fulfilled_policies |= PolicyFlags::PUBLIC;
   }
 
-  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error("failed to load apk %s", target_apk_path.c_str());
+  const auto target = TargetResourceContainer::FromPath(target_apk_path);
+  if (!target) {
+    return Error("failed to load target '%s'", target_apk_path.c_str());
   }
 
-  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error("failed to load apk %s", overlay_apk_path.c_str());
+  const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  if (!overlay) {
+    return Error("failed to load apk overlay '%s'", overlay_apk_path.c_str());
   }
 
-  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, overlay_name,
-                                          fulfilled_policies, !ignore_overlayable);
+  const auto idmap = Idmap::FromContainers(**target, **overlay, overlay_name, fulfilled_policies,
+                                           !ignore_overlayable);
   if (!idmap) {
     return Error(idmap.GetError(), "failed to create idmap");
   }
@@ -112,13 +112,14 @@
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
-    return Error("failed to open idmap path %s", idmap_path.c_str());
+    return Error("failed to open idmap path '%s'", idmap_path.c_str());
   }
+
   BinaryStreamVisitor visitor(fout);
   (*idmap)->accept(&visitor);
   fout.close();
   if (fout.fail()) {
-    return Error("failed to write to idmap path %s", idmap_path.c_str());
+    return Error("failed to write to idmap path '%s'", idmap_path.c_str());
   }
 
   return Unit{};
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 5db391c..953d99f 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -34,13 +34,14 @@
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/SysTrace.h"
 
-using android::ApkAssets;
 using android::base::StringPrintf;
 using android::idmap2::BinaryStreamVisitor;
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::Unit;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
@@ -91,9 +92,9 @@
     fulfilled_policies |= PolicyFlags::PUBLIC;
   }
 
-  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error("failed to load apk %s", target_apk_path.c_str());
+  const auto target = TargetResourceContainer::FromPath(target_apk_path);
+  if (!target) {
+    return Error("failed to load target '%s'", target_apk_path.c_str());
   }
 
   std::vector<std::string> idmap_paths;
@@ -108,14 +109,14 @@
     // TODO(b/175014391): Support multiple overlay tags in OverlayConfig
     if (!Verify(idmap_path, target_apk_path, overlay_apk_path, "", fulfilled_policies,
                 !ignore_overlayable)) {
-      const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-      if (!overlay_apk) {
+      const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+      if (!overlay) {
         LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
         continue;
       }
 
-      const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", fulfilled_policies,
-                                              !ignore_overlayable);
+      const auto idmap =
+          Idmap::FromContainers(**target, **overlay, "", fulfilled_policies, !ignore_overlayable);
       if (!idmap) {
         LOG(WARNING) << "failed to create idmap";
         continue;
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index 43a1951..f41e57c 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -37,7 +37,6 @@
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
 #include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
 #include "utils/String16.h"
 #include "utils/String8.h"
 
@@ -52,10 +51,10 @@
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::IdmapHeader;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::ResourceId;
 using android::idmap2::Result;
 using android::idmap2::Unit;
-using android::idmap2::utils::ExtractOverlayManifestInfo;
 
 namespace {
 
@@ -195,12 +194,17 @@
       }
       apk_assets.push_back(std::move(target_apk));
 
-      auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath(),
-                                                      idmap_header->GetOverlayName());
+      auto overlay = OverlayResourceContainer::FromPath(idmap_header->GetOverlayPath());
+      if (!overlay) {
+        return overlay.GetError();
+      }
+
+      auto manifest_info = (*overlay)->FindOverlayInfo(idmap_header->GetOverlayName());
       if (!manifest_info) {
         return manifest_info.GetError();
       }
-      target_package_name = manifest_info->target_package;
+
+      target_package_name = (*manifest_info).target_package;
     } else if (target_path != idmap_header->GetTargetPath()) {
       return Error("different target APKs (expected target APK %s but %s has target APK %s)",
                    target_path.c_str(), idmap_path.c_str(), idmap_header->GetTargetPath().c_str());
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 93537d3..05336ba 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -18,10 +18,10 @@
 
 #include <sys/stat.h>   // umask
 #include <sys/types.h>  // umask
-#include <unistd.h>
 
 #include <cerrno>
 #include <cstring>
+#include <filesystem>
 #include <fstream>
 #include <memory>
 #include <ostream>
@@ -35,19 +35,20 @@
 #include "idmap2/Idmap.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "idmap2/ZipFile.h"
-#include "utils/String8.h"
 
 using android::IPCThreadState;
 using android::base::StringPrintf;
 using android::binder::Status;
 using android::idmap2::BinaryStreamVisitor;
-using android::idmap2::GetPackageCrc;
+using android::idmap2::FabricatedOverlay;
+using android::idmap2::FabricatedOverlayContainer;
 using android::idmap2::Idmap;
 using android::idmap2::IdmapHeader;
-using android::idmap2::ZipFile;
+using android::idmap2::OverlayResourceContainer;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::RandomStringForPath;
 using android::idmap2::utils::UidHasWriteAccessToPath;
 
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
@@ -69,39 +70,24 @@
   return static_cast<PolicyBitmask>(arg);
 }
 
-Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
-  const auto zip = ZipFile::Open(apk_path);
-  if (!zip) {
-    return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
-  }
-
-  const auto crc = GetPackageCrc(*zip);
-  if (!crc) {
-    return error(crc.GetErrorMessage());
-  }
-
-  *out_crc = *crc;
-  return ok();
-}
-
 }  // namespace
 
 namespace android::os {
 
-Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
+Status Idmap2Service::getIdmapPath(const std::string& overlay_path,
                                    int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
   assert(_aidl_return);
-  SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_apk_path;
-  *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_path;
+  *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   return ok();
 }
 
-Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
-                                  int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+Status Idmap2Service::removeIdmap(const std::string& overlay_path, int32_t user_id ATTRIBUTE_UNUSED,
+                                  bool* _aidl_return) {
   assert(_aidl_return);
-  SYSTRACE << "Idmap2Service::removeIdmap " << overlay_apk_path;
+  SYSTRACE << "Idmap2Service::removeIdmap " << overlay_path;
   const uid_t uid = IPCThreadState::self()->getCallingUid();
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   if (!UidHasWriteAccessToPath(uid, idmap_path)) {
     *_aidl_return = false;
     return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
@@ -115,93 +101,88 @@
   return ok();
 }
 
-Status Idmap2Service::verifyIdmap(const std::string& target_apk_path,
-                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
+Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::string& overlay_path,
+                                  const std::string& overlay_name, int32_t fulfilled_policies,
                                   bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
                                   bool* _aidl_return) {
-  SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
+  SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_path;
   assert(_aidl_return);
 
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
   fin.close();
   if (!header) {
     *_aidl_return = false;
-    return error("failed to parse idmap header");
+    LOG(WARNING) << "failed to parse idmap header of '" << idmap_path << "'";
+    return ok();
   }
 
-  uint32_t target_crc;
-  if (target_apk_path == kFrameworkPath && android_crc_) {
-    target_crc = *android_crc_;
-  } else {
-    auto target_crc_status = GetCrc(target_apk_path, &target_crc);
-    if (!target_crc_status.isOk()) {
-      *_aidl_return = false;
-      return target_crc_status;
-    }
-
-    // Loading the framework zip can take several milliseconds. Cache the crc of the framework
-    // resource APK to reduce repeated work during boot.
-    if (target_apk_path == kFrameworkPath) {
-      android_crc_ = target_crc;
-    }
-  }
-
-  uint32_t overlay_crc;
-  auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc);
-  if (!overlay_crc_status.isOk()) {
+  const auto target = GetTargetContainer(target_path);
+  if (!target) {
     *_aidl_return = false;
-    return overlay_crc_status;
+    LOG(WARNING) << "failed to load target '" << target_path << "'";
+    return ok();
   }
 
-  // TODO(162841629): Support passing overlay name to idmap2d verify
+  const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    *_aidl_return = false;
+    LOG(WARNING) << "failed to load overlay '" << overlay_path << "'";
+    return ok();
+  }
+
   auto up_to_date =
-      header->IsUpToDate(target_apk_path, overlay_apk_path, "", target_crc, overlay_crc,
+      header->IsUpToDate(*GetPointer(*target), **overlay, overlay_name,
                          ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
 
   *_aidl_return = static_cast<bool>(up_to_date);
-  return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage());
+  if (!up_to_date) {
+    LOG(WARNING) << "idmap '" << idmap_path
+                 << "' not up to date : " << up_to_date.GetErrorMessage();
+  }
+  return ok();
 }
 
-Status Idmap2Service::createIdmap(const std::string& target_apk_path,
-                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
+Status Idmap2Service::createIdmap(const std::string& target_path, const std::string& overlay_path,
+                                  const std::string& overlay_name, int32_t fulfilled_policies,
                                   bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
                                   std::optional<std::string>* _aidl_return) {
   assert(_aidl_return);
-  SYSTRACE << "Idmap2Service::createIdmap " << target_apk_path << " " << overlay_apk_path;
+  SYSTRACE << "Idmap2Service::createIdmap " << target_path << " " << overlay_path;
   _aidl_return->reset();
 
   const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
 
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   const uid_t uid = IPCThreadState::self()->getCallingUid();
   if (!UidHasWriteAccessToPath(uid, idmap_path)) {
     return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
                                     idmap_path.c_str(), uid));
   }
 
-  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return error("failed to load apk " + target_apk_path);
+  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+  // that existing memory maps will continue to be valid and unaffected. The file must be deleted
+  // before attempting to create the idmap, so that if idmap  creation fails, the overlay will no
+  // longer be usable.
+  unlink(idmap_path.c_str());
+
+  const auto target = GetTargetContainer(target_path);
+  if (!target) {
+    return error("failed to load target '%s'" + target_path);
   }
 
-  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return error("failed to load apk " + overlay_apk_path);
+  const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return error("failed to load apk overlay '%s'" + overlay_path);
   }
 
-  // TODO(162841629): Support passing overlay name to idmap2d create
-  const auto idmap =
-      Idmap::FromApkAssets(*target_apk, *overlay_apk, "", policy_bitmask, enforce_overlayable);
+  const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, overlay_name,
+                                           policy_bitmask, enforce_overlayable);
   if (!idmap) {
     return error(idmap.GetErrorMessage());
   }
 
-  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
-  // that existing memory maps will continue to be valid and unaffected.
-  unlink(idmap_path.c_str());
-
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
@@ -220,4 +201,155 @@
   return ok();
 }
 
+idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer(
+    const std::string& target_path) {
+  if (target_path == kFrameworkPath) {
+    if (framework_apk_cache_ == nullptr) {
+      // Initialize the framework APK cache.
+      auto target = TargetResourceContainer::FromPath(target_path);
+      if (!target) {
+        return target.GetError();
+      }
+      framework_apk_cache_ = std::move(*target);
+    }
+    return {framework_apk_cache_.get()};
+  }
+
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return target.GetError();
+  }
+  return {std::move(*target)};
+}
+
+Status Idmap2Service::createFabricatedOverlay(
+    const os::FabricatedOverlayInternal& overlay,
+    std::optional<os::FabricatedOverlayInfo>* _aidl_return) {
+  idmap2::FabricatedOverlay::Builder builder(overlay.packageName, overlay.overlayName,
+                                             overlay.targetPackageName);
+  if (!overlay.targetOverlayable.empty()) {
+    builder.SetOverlayable(overlay.targetOverlayable);
+  }
+
+  for (const auto& res : overlay.entries) {
+    builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+  }
+
+  // Generate the file path of the fabricated overlay and ensure it does not collide with an
+  // existing path. Re-registering a fabricated overlay will always result in an updated path.
+  std::string path;
+  std::string file_name;
+  do {
+    constexpr size_t kSuffixLength = 4;
+    const std::string random_suffix = RandomStringForPath(kSuffixLength);
+    file_name = StringPrintf("%s-%s-%s.frro", overlay.packageName.c_str(),
+                             overlay.overlayName.c_str(), random_suffix.c_str());
+    path = StringPrintf("%s/%s", kIdmapCacheDir, file_name.c_str());
+
+    // Invoking std::filesystem::exists with a file name greater than 255 characters will cause this
+    // process to abort since the name exceeds the maximum file name size.
+    const size_t kMaxFileNameLength = 255;
+    if (file_name.size() > kMaxFileNameLength) {
+      return error(
+          base::StringPrintf("fabricated overlay file name '%s' longer than %zu characters",
+                             file_name.c_str(), kMaxFileNameLength));
+    }
+  } while (std::filesystem::exists(path));
+
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+  if (!UidHasWriteAccessToPath(uid, path)) {
+    return error(base::StringPrintf("will not write to %s: calling uid %d lacks write access",
+                                    path.c_str(), uid));
+  }
+
+  // Persist the fabricated overlay.
+  umask(kIdmapFilePermissionMask);
+  std::ofstream fout(path);
+  if (fout.fail()) {
+    return error("failed to open frro path " + path);
+  }
+  const auto frro = builder.Build();
+  if (!frro) {
+    return error(StringPrintf("failed to serialize '%s:%s': %s", overlay.packageName.c_str(),
+                              overlay.overlayName.c_str(), frro.GetErrorMessage().c_str()));
+  }
+  auto result = frro->ToBinaryStream(fout);
+  if (!result) {
+    unlink(path.c_str());
+    return error("failed to write to frro path " + path + ": " + result.GetErrorMessage());
+  }
+  if (fout.fail()) {
+    unlink(path.c_str());
+    return error("failed to write to frro path " + path);
+  }
+
+  os::FabricatedOverlayInfo out_info;
+  out_info.packageName = overlay.packageName;
+  out_info.overlayName = overlay.overlayName;
+  out_info.targetPackageName = overlay.targetPackageName;
+  out_info.targetOverlayable = overlay.targetOverlayable;
+  out_info.path = path;
+  *_aidl_return = out_info;
+  return ok();
+}
+
+Status Idmap2Service::getFabricatedOverlayInfos(
+    std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+  for (const auto& entry : std::filesystem::directory_iterator(kIdmapCacheDir)) {
+    if (!android::IsFabricatedOverlay(entry.path())) {
+      continue;
+    }
+
+    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+    if (!overlay) {
+      // This is a sign something went wrong.
+      LOG(ERROR) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
+      continue;
+    }
+
+    const auto info = (*overlay)->GetManifestInfo();
+    os::FabricatedOverlayInfo out_info;
+    out_info.packageName = info.package_name;
+    out_info.overlayName = info.name;
+    out_info.targetPackageName = info.target_package;
+    out_info.targetOverlayable = info.target_name;
+    out_info.path = entry.path();
+    _aidl_return->emplace_back(std::move(out_info));
+  }
+
+  return ok();
+}
+
+binder::Status Idmap2Service::deleteFabricatedOverlay(const std::string& overlay_path,
+                                                      bool* _aidl_return) {
+  SYSTRACE << "Idmap2Service::deleteFabricatedOverlay " << overlay_path;
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+
+  if (!UidHasWriteAccessToPath(uid, overlay_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    overlay_path.c_str(), uid));
+  }
+
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
+  if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    idmap_path.c_str(), uid));
+  }
+
+  if (unlink(overlay_path.c_str()) != 0) {
+    *_aidl_return = false;
+    return error("failed to unlink " + overlay_path + ": " + strerror(errno));
+  }
+
+  if (unlink(idmap_path.c_str()) != 0) {
+    *_aidl_return = false;
+    return error("failed to unlink " + idmap_path + ": " + strerror(errno));
+  }
+
+  *_aidl_return = true;
+  return ok();
+}
+
 }  // namespace android::os
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 0127e87..4d16ff3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -18,12 +18,14 @@
 #define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
 
 #include <android-base/unique_fd.h>
+#include <android/os/BnIdmap2.h>
+#include <android/os/FabricatedOverlayInfo.h>
 #include <binder/BinderService.h>
+#include <idmap2/ResourceContainer.h>
+#include <idmap2/Result.h>
 
 #include <string>
 
-#include "android/os/BnIdmap2.h"
-
 namespace android::os {
 
 class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
@@ -32,27 +34,58 @@
     return "idmap";
   }
 
-  binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
+  binder::Status getIdmapPath(const std::string& overlay_path, int32_t user_id,
                               std::string* _aidl_return) override;
 
-  binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
+  binder::Status removeIdmap(const std::string& overlay_path, int32_t user_id,
                              bool* _aidl_return) override;
 
-  binder::Status verifyIdmap(const std::string& target_apk_path,
-                             const std::string& overlay_apk_path, int32_t fulfilled_policies,
+  binder::Status verifyIdmap(const std::string& target_path, const std::string& overlay_path,
+                             const std::string& overlay_name, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
                              bool* _aidl_return) override;
 
-  binder::Status createIdmap(const std::string& target_apk_path,
-                             const std::string& overlay_apk_path, int32_t fulfilled_policies,
+  binder::Status createIdmap(const std::string& target_path, const std::string& overlay_path,
+                             const std::string& overlay_name, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
                              std::optional<std::string>* _aidl_return) override;
 
+  binder::Status createFabricatedOverlay(
+      const os::FabricatedOverlayInternal& overlay,
+      std::optional<os::FabricatedOverlayInfo>* _aidl_return) override;
+
+  binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
+                                         bool* _aidl_return) override;
+
+  binder::Status getFabricatedOverlayInfos(
+      std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
+
  private:
-  // Cache the crc of the android framework package since the crc cannot change without a reboot.
-  std::optional<uint32_t> android_crc_;
+  // idmap2d is killed after a period of inactivity, so any information stored on this class should
+  // be able to be recalculated if idmap2 dies and restarts.
+  std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
+
+  std::vector<os::FabricatedOverlayInfo> fabricated_overlays_;
+
+  template <typename T>
+  using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
+
+  using TargetResourceContainerPtr = MaybeUniquePtr<idmap2::TargetResourceContainer>;
+  idmap2::Result<TargetResourceContainerPtr> GetTargetContainer(const std::string& target_path);
+
+  template <typename T>
+  WARN_UNUSED static const T* GetPointer(const MaybeUniquePtr<T>& ptr);
 };
 
+template <typename T>
+const T* Idmap2Service::GetPointer(const MaybeUniquePtr<T>& ptr) {
+  auto u = std::get_if<T*>(&ptr);
+  if (u != nullptr) {
+    return *u;
+  }
+  return std::get<std::unique_ptr<T>>(ptr).get();
+}
+
 }  // namespace android::os
 
 #endif  // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/tools/hiddenapi/Android.bp b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
similarity index 64%
copy from tools/hiddenapi/Android.bp
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
index e0eb06cb..6375d24 100644
--- a/tools/hiddenapi/Android.bp
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-python_binary_host {
-	name: "merge_csv",
-	main: "merge_csv.py",
-	srcs: ["merge_csv.py"],
-	version: {
-		py2: {
-			enabled: false,
-		},
-		py3: {
-			enabled: true,
-			embedded_launcher: true
-		},
-	},
-}
+package android.os;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInfo {
+    @utf8InCpp String path;
+    @utf8InCpp String packageName;
+    @utf8InCpp String overlayName;
+    @utf8InCpp String targetPackageName;
+    @utf8InCpp String targetOverlayable;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
new file mode 100644
index 0000000..f67d8be
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.FabricatedOverlayInternalEntry;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternal {
+    @utf8InCpp String packageName;
+    @utf8InCpp String overlayName;
+    @utf8InCpp String targetPackageName;
+    @utf8InCpp String targetOverlayable;
+    List<FabricatedOverlayInternalEntry> entries;
+}
\ No newline at end of file
diff --git a/tools/hiddenapi/Android.bp b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index e0eb06cb..6c2af27 100644
--- a/tools/hiddenapi/Android.bp
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-python_binary_host {
-	name: "merge_csv",
-	main: "merge_csv.py",
-	srcs: ["merge_csv.py"],
-	version: {
-		py2: {
-			enabled: false,
-		},
-		py3: {
-			enabled: true,
-			embedded_launcher: true
-		},
-	},
-}
+package android.os;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternalEntry {
+    @utf8InCpp String resourceName;
+    int dataType;
+    int data;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
similarity index 73%
rename from cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 156f1d7..35bca98 100644
--- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInfo;
+
 /**
  * @hide
  */
@@ -23,13 +26,18 @@
   @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
   boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
   boolean verifyIdmap(@utf8InCpp String targetApkPath,
-					  @utf8InCpp String overlayApkPath,
+                      @utf8InCpp String overlayApkPath,
+                      @utf8InCpp String overlayName,
                       int fulfilledPolicies,
                       boolean enforceOverlayable,
                       int userId);
   @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
                                           @utf8InCpp String overlayApkPath,
+                                          @utf8InCpp String overlayName,
                                           int fulfilledPolicies,
                                           boolean enforceOverlayable,
                                           int userId);
+  @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
+  List<FabricatedOverlayInfo> getFabricatedOverlayInfos();
+  boolean deleteFabricatedOverlay(@utf8InCpp String path);
 }
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
similarity index 100%
rename from cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
new file mode 100644
index 0000000..be687d9
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
+#define IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
+
+#include <libidmap2/proto/fabricated_v1.pb.h>
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include "idmap2/ResourceContainer.h"
+#include "idmap2/Result.h"
+
+namespace android::idmap2 {
+
+struct FabricatedOverlay {
+  struct Builder {
+    Builder(const std::string& package_name, const std::string& name,
+            const std::string& target_package_name);
+
+    Builder& SetOverlayable(const std::string& name);
+
+    Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
+                              uint32_t data_value);
+
+    WARN_UNUSED Result<FabricatedOverlay> Build();
+
+   private:
+    struct Entry {
+      std::string resource_name;
+      DataType data_type;
+      DataValue data_value;
+    };
+
+    std::string package_name_;
+    std::string name_;
+    std::string target_package_name_;
+    std::string target_overlayable_;
+    std::vector<Entry> entries_;
+  };
+
+  Result<Unit> ToBinaryStream(std::ostream& stream) const;
+  static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream);
+
+ private:
+  struct SerializedData {
+    std::unique_ptr<uint8_t[]> data;
+    size_t data_size;
+    uint32_t crc;
+  };
+
+  Result<SerializedData*> InitializeData() const;
+  Result<uint32_t> GetCrc() const;
+
+  FabricatedOverlay(pb::FabricatedOverlay&& overlay, std::optional<uint32_t> crc_from_disk = {});
+
+  pb::FabricatedOverlay overlay_pb_;
+  std::optional<uint32_t> crc_from_disk_;
+  mutable std::optional<SerializedData> data_;
+
+  friend struct FabricatedOverlayContainer;
+};
+
+struct FabricatedOverlayContainer : public OverlayResourceContainer {
+  static Result<std::unique_ptr<FabricatedOverlayContainer>> FromPath(std::string path);
+  static std::unique_ptr<FabricatedOverlayContainer> FromOverlay(FabricatedOverlay&& overlay);
+
+  WARN_UNUSED OverlayManifestInfo GetManifestInfo() const;
+
+  // inherited from OverlayResourceContainer
+  WARN_UNUSED Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
+  WARN_UNUSED Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
+
+  // inherited from ResourceContainer
+  WARN_UNUSED Result<uint32_t> GetCrc() const override;
+  WARN_UNUSED const std::string& GetPath() const override;
+  WARN_UNUSED Result<std::string> GetResourceName(ResourceId id) const override;
+
+  ~FabricatedOverlayContainer() override;
+
+ private:
+  FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path);
+  FabricatedOverlay overlay_;
+  std::string path_;
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index c4e0e1f..2588688 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,7 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 
+#include <random>
 #include <string>
 
 namespace android::idmap2::utils {
@@ -26,6 +27,8 @@
 
 bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
 
+std::string RandomStringForPath(size_t length);
+
 }  // namespace android::idmap2::utils
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 1b815c1..58aff42 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -23,8 +23,8 @@
  *                               debug_info
  * data                       := data_header target_entry* target_inline_entry* overlay_entry*
  *                               string_pool
- * data_header                := target_package_id overlay_package_id padding(2) target_entry_count
- *                               target_inline_entry_count overlay_entry_count string_pool_index
+ * data_header                := target_entry_count target_inline_entry_count overlay_entry_count
+ *                               string_pool_index
  * target_entry               := target_id overlay_id
  * target_inline_entry        := target_id Res_value::size padding(1) Res_value::type
  *                               Res_value::value
@@ -68,11 +68,10 @@
 #include <vector>
 
 #include "android-base/macros.h"
-#include "androidfw/ApkAssets.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/StringPiece.h"
+#include "idmap2/ResourceContainer.h"
 #include "idmap2/ResourceMapping.h"
-#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
@@ -85,9 +84,6 @@
 // current version of the idmap binary format; must be incremented when the format is changed
 static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
 
-// Retrieves a crc generated using all of the files within the zip that can affect idmap generation.
-Result<uint32_t> GetPackageCrc(const ZipFile& zip_info);
-
 class IdmapHeader {
  public:
   static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
@@ -135,9 +131,9 @@
   // Invariant: anytime the idmap data encoding is changed, the idmap version
   // field *must* be incremented. Because of this, we know that if the idmap
   // header is up-to-date the entire file is up-to-date.
-  Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
-                          const std::string& overlay_name, PolicyBitmask fulfilled_policies,
-                          bool enforce_overlayable) const;
+  Result<Unit> IsUpToDate(const TargetResourceContainer& target,
+                          const OverlayResourceContainer& overlay, const std::string& overlay_name,
+                          PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
 
   Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
                           const std::string& overlay_name, uint32_t target_crc,
@@ -169,14 +165,6 @@
    public:
     static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
 
-    inline PackageId GetTargetPackageId() const {
-      return target_package_id_;
-    }
-
-    inline PackageId GetOverlayPackageId() const {
-      return overlay_package_id_;
-    }
-
     inline uint32_t GetTargetEntryCount() const {
       return target_entry_count;
     }
@@ -196,8 +184,6 @@
     void accept(Visitor* v) const;
 
    private:
-    PackageId target_package_id_;
-    PackageId overlay_package_id_;
     uint32_t target_entry_count;
     uint32_t target_entry_inline_count;
     uint32_t overlay_entry_count;
@@ -275,11 +261,10 @@
   // file is used; change this in the next version of idmap to use a named
   // package instead; also update FromApkAssets to take additional parameters:
   // the target and overlay package names
-  static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
-                                                            const ApkAssets& overlay_apk_assets,
-                                                            const std::string& overlay_name,
-                                                            const PolicyBitmask& fulfilled_policies,
-                                                            bool enforce_overlayable);
+  static Result<std::unique_ptr<const Idmap>> FromContainers(
+      const TargetResourceContainer& target, const OverlayResourceContainer& overlay,
+      const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
+      bool enforce_overlayable);
 
   const std::unique_ptr<const IdmapHeader>& GetHeader() const {
     return header_;
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 2b4c761..4464201 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -41,9 +41,8 @@
 
  private:
   std::ostream& stream_;
-  AssetManager2 target_am_;
-  AssetManager2 overlay_am_;
-  std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
+  std::unique_ptr<TargetResourceContainer> target_;
+  std::unique_ptr<OverlayResourceContainer> overlay_;
 };
 
 }  // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 4583516..ebd0d1e 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -49,10 +49,9 @@
   void pad(size_t padding);
 
   std::ostream& stream_;
-  std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
-  AssetManager2 target_am_;
-  AssetManager2 overlay_am_;
   size_t offset_;
+  std::unique_ptr<TargetResourceContainer> target_;
+  std::unique_ptr<OverlayResourceContainer> overlay_;
 };
 
 }  // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
new file mode 100644
index 0000000..74a6f56
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "idmap2/Policies.h"
+#include "idmap2/ResourceUtils.h"
+
+namespace android::idmap2 {
+
+struct ResourceContainer {
+  WARN_UNUSED virtual Result<uint32_t> GetCrc() const = 0;
+  WARN_UNUSED virtual const std::string& GetPath() const = 0;
+  WARN_UNUSED virtual Result<std::string> GetResourceName(ResourceId id) const = 0;
+
+  virtual ~ResourceContainer() = default;
+};
+
+struct TargetResourceContainer : public ResourceContainer {
+  static Result<std::unique_ptr<TargetResourceContainer>> FromPath(std::string path);
+
+  WARN_UNUSED virtual Result<bool> DefinesOverlayable() const = 0;
+  WARN_UNUSED virtual Result<const android::OverlayableInfo*> GetOverlayableInfo(
+      ResourceId id) const = 0;
+  WARN_UNUSED virtual Result<ResourceId> GetResourceId(const std::string& name) const = 0;
+
+  ~TargetResourceContainer() override = default;
+};
+
+struct OverlayManifestInfo {
+  std::string package_name;     // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string name;             // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_package;   // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_name;      // NOLINT(misc-non-private-member-variables-in-classes)
+  ResourceId resource_mapping;  // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+struct OverlayData {
+  struct ResourceIdValue {
+    // The overlay resource id.
+    ResourceId overlay_id;
+
+    // Whether or not references to the overlay resource id should be rewritten to its corresponding
+    // target id during resource resolution.
+    bool rewrite_id;
+  };
+
+  struct Value {
+    std::string resource_name;
+    std::variant<ResourceIdValue, TargetValue> value;
+  };
+
+  struct InlineStringPoolData {
+    // The binary data of the android::ResStringPool string pool.
+    std::unique_ptr<uint8_t[]> data;
+
+    // The length of the binary data.
+    uint32_t data_length;
+
+    // The offset added to TargetValue#data_value (the index of the string in the inline string
+    // pool) in order to prevent the indices of the overlay resource table string pool from
+    // colliding with the inline string pool indices.
+    uint32_t string_pool_offset;
+  };
+
+  // The overlay's mapping of target resource name to overlaid value. Use a vector to enforce that
+  // the overlay pairs are inserted into the ResourceMapping in the specified ordered.
+  std::vector<Value> pairs;
+
+  // If the overlay maps a target resource to a string literal (not a string resource), then the
+  // this field contains information about the string pool in which the string literal resides so it
+  // can be inlined into an idmap.
+  std::optional<InlineStringPoolData> string_pool_data;
+};
+
+struct OverlayResourceContainer : public ResourceContainer {
+  static Result<std::unique_ptr<OverlayResourceContainer>> FromPath(std::string path);
+
+  WARN_UNUSED virtual Result<OverlayManifestInfo> FindOverlayInfo(
+      const std::string& name) const = 0;
+  WARN_UNUSED virtual Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const = 0;
+
+  ~OverlayResourceContainer() override = default;
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
index f66916c..5a0a384 100644
--- a/cmds/idmap2/include/idmap2/ResourceMapping.h
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -17,30 +17,22 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
 #define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
 
+#include <androidfw/ApkAssets.h>
+
 #include <map>
 #include <memory>
 #include <utility>
 
-#include "androidfw/ApkAssets.h"
+#include "idmap2/FabricatedOverlay.h"
 #include "idmap2/LogInfo.h"
 #include "idmap2/Policies.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/XmlParser.h"
 
-using android::idmap2::utils::OverlayManifestInfo;
-
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 
 namespace android::idmap2 {
-
-struct TargetValue {
-  typedef uint8_t DataType;
-  typedef uint32_t DataValue;
-  DataType data_type;
-  DataValue data_value;
-};
-
 using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, TargetValue>>;
 using OverlayResourceMap = std::map<ResourceId, ResourceId>;
 
@@ -49,94 +41,60 @@
   // Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to
   // `false` disables all overlayable and policy enforcement: this is intended for backwards
   // compatibility pre-Q and unit tests.
-  static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets,
-                                               const ApkAssets& overlay_apk_assets,
-                                               const OverlayManifestInfo& overlay_info,
-                                               const PolicyBitmask& fulfilled_policies,
-                                               bool enforce_overlayable, LogInfo& log_info);
+  static Result<ResourceMapping> FromContainers(const TargetResourceContainer& target,
+                                                const OverlayResourceContainer& overlay,
+                                                const OverlayManifestInfo& overlay_info,
+                                                const PolicyBitmask& fulfilled_policies,
+                                                bool enforce_overlayable, LogInfo& log_info);
 
   // Retrieves the mapping of target resource id to overlay value.
-  inline const TargetResourceMap& GetTargetToOverlayMap() const {
-    return target_map_;
-  }
+  WARN_UNUSED const TargetResourceMap& GetTargetToOverlayMap() const;
 
   // Retrieves the mapping of overlay resource id to target resource id. This allows a reference to
   // an overlay resource to appear as a reference to its corresponding target resource at runtime.
-  OverlayResourceMap GetOverlayToTargetMap() const;
-
-  // Retrieves the build-time package id of the target package.
-  inline uint32_t GetTargetPackageId() const {
-    return target_package_id_;
-  }
-
-  // Retrieves the build-time package id of the overlay package.
-  inline uint32_t GetOverlayPackageId() const {
-    return overlay_package_id_;
-  }
+  WARN_UNUSED const OverlayResourceMap& GetOverlayToTargetMap() const;
 
   // Retrieves the offset that was added to the index of inline string overlay values so the indices
   // do not collide with the indices of the overlay resource table string pool.
-  inline uint32_t GetStringPoolOffset() const {
-    return string_pool_offset_;
-  }
+  WARN_UNUSED uint32_t GetStringPoolOffset() const;
 
   // Retrieves the raw string pool data from the xml referenced in android:resourcesMap.
-  inline const StringPiece GetStringPoolData() const {
-    return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
-                       string_pool_data_length_);
-  }
+  WARN_UNUSED StringPiece GetStringPoolData() const;
 
  private:
   ResourceMapping() = default;
 
-  // Maps a target resource id to an overlay resource id.
-  // If rewrite_overlay_reference is `true` then references to the overlay
-  // resource should appear as a reference to its corresponding target resource at runtime.
-  Result<Unit> AddMapping(ResourceId target_resource, ResourceId overlay_resource,
-                          bool rewrite_overlay_reference);
-
-  // Maps a target resource id to a data type and value combination.
-  // The `data_type` is the runtime format of the data value (see Res_value::dataType).
-  Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type,
-                          TargetValue::DataValue data_value);
-
-  // Removes the overlay value mapping for the target resource.
-  void RemoveMapping(ResourceId target_resource);
-
-  // Parses the mapping of target resources to overlay resources to generate a ResourceMapping.
-  static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am,
-                                                       const LoadedPackage* target_package,
-                                                       const LoadedPackage* overlay_package,
-                                                       size_t string_pool_offset,
-                                                       const XmlParser& overlay_parser,
-                                                       LogInfo& log_info);
-
-  // Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
-  // a target resource, a resource must exist in the overlay with the same type and entry name as
-  // the target resource.
-  static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am,
-                                                             const AssetManager2* overlay_am,
-                                                             const LoadedPackage* target_package,
-                                                             const LoadedPackage* overlay_package,
-                                                             LogInfo& log_info);
-
-  // Removes resources that do not pass policy or overlayable checks of the target package.
-  void FilterOverlayableResources(const AssetManager2* target_am,
-                                  const LoadedPackage* target_package,
-                                  const LoadedPackage* overlay_package,
-                                  const OverlayManifestInfo& overlay_info,
-                                  const PolicyBitmask& fulfilled_policies, LogInfo& log_info);
+  // Maps a target resource id to an overlay resource id or a android::Res_value value.
+  //
+  // If `allow_rewriting_` is true, then the overlay-to-target map will be populated if the target
+  // resource id is mapped to an overlay resource id.
+  Result<Unit> AddMapping(ResourceId target_resource,
+                          const std::variant<OverlayData::ResourceIdValue, TargetValue>& value);
 
   TargetResourceMap target_map_;
-  std::multimap<ResourceId, ResourceId> overlay_map_;
-
-  uint32_t target_package_id_ = 0;
-  uint32_t overlay_package_id_ = 0;
+  OverlayResourceMap overlay_map_;
   uint32_t string_pool_offset_ = 0;
   uint32_t string_pool_data_length_ = 0;
   std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr;
 };
 
+inline const TargetResourceMap& ResourceMapping::GetTargetToOverlayMap() const {
+  return target_map_;
+}
+
+inline const OverlayResourceMap& ResourceMapping::GetOverlayToTargetMap() const {
+  return overlay_map_;
+}
+
+inline uint32_t ResourceMapping::GetStringPoolOffset() const {
+  return string_pool_offset_;
+}
+
+inline StringPiece ResourceMapping::GetStringPoolData() const {
+  return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
+                     string_pool_data_length_);
+}
+
 }  // namespace android::idmap2
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index cd14d3e..a0202df 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -22,19 +22,26 @@
 
 #include "androidfw/AssetManager2.h"
 #include "idmap2/Result.h"
-#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
-// use typedefs to let the compiler warn us about implicit casts
-typedef uint32_t ResourceId;  // 0xpptteeee
-typedef uint8_t PackageId;    // pp in 0xpptteeee
-typedef uint8_t TypeId;       // tt in 0xpptteeee
-typedef uint16_t EntryId;     // eeee in 0xpptteeee
-
 #define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
 #define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
 
+// use typedefs to let the compiler warn us about implicit casts
+using ResourceId = uint32_t;  // 0xpptteeee
+using PackageId = uint8_t;    // pp in 0xpptteeee
+using TypeId = uint8_t;       // tt in 0xpptteeee
+using EntryId = uint16_t;     // eeee in 0xpptteeee
+
+using DataType = uint8_t;    // Res_value::dataType
+using DataValue = uint32_t;  // Res_value::data
+
+struct TargetValue {
+  DataType data_type;
+  DataValue data_value;
+};
+
 namespace utils {
 
 // Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
@@ -43,20 +50,10 @@
 // Converts the Res_value::data_type to a human-readable string representation.
 StringPiece DataTypeToString(uint8_t data_type);
 
-struct OverlayManifestInfo {
-  std::string name;            // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_package;  // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_name;     // NOLINT(misc-non-private-member-variables-in-classes)
-  uint32_t resource_mapping;   // NOLINT(misc-non-private-member-variables-in-classes)
-};
-
-Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
-                                                       const std::string& name);
-
+// Retrieves the type and entry name of the resource in the AssetManager in the form type/entry.
 Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
 
 }  // namespace utils
-
 }  // namespace android::idmap2
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
index 1c74ab3..c968a5e 100644
--- a/cmds/idmap2/include/idmap2/XmlParser.h
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -30,8 +30,7 @@
 
 namespace android::idmap2 {
 
-class XmlParser {
- public:
+struct XmlParser {
   using Event = ResXMLParser::event_code_t;
   class iterator;
 
@@ -127,23 +126,19 @@
   };
 
   // Creates a new xml parser beginning at the first tag.
-  static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size,
-                                                         bool copy_data = false);
-  ~XmlParser();
+  static Result<XmlParser> Create(const void* data, size_t size, bool copy_data = false);
 
   inline iterator tree_iterator() const {
-    return iterator(tree_);
+    return iterator(*tree_);
   }
 
   inline const ResStringPool& get_strings() const {
-    return tree_.getStrings();
+    return tree_->getStrings();
   }
 
  private:
-  XmlParser() = default;
-  mutable ResXMLTree tree_;
-
-  DISALLOW_COPY_AND_ASSIGN(XmlParser);
+  explicit XmlParser(std::unique_ptr<ResXMLTree> tree);
+  mutable std::unique_ptr<ResXMLTree> tree_;
 };
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h
deleted file mode 100644
index 8f50e36..0000000
--- a/cmds/idmap2/include/idmap2/ZipFile.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
-#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
-
-#include <memory>
-#include <string>
-
-#include "android-base/macros.h"
-#include "idmap2/Result.h"
-#include "ziparchive/zip_archive.h"
-
-namespace android::idmap2 {
-
-struct MemoryChunk {
-  size_t size;
-  uint8_t buf[0];
-
-  static std::unique_ptr<MemoryChunk> Allocate(size_t size);
-
- private:
-  MemoryChunk() {
-  }
-};
-
-class ZipFile {
- public:
-  static std::unique_ptr<const ZipFile> Open(const std::string& path);
-
-  std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const;
-  Result<uint32_t> Crc(const std::string& entryPath) const;
-
-  ~ZipFile();
-
- private:
-  explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) {
-  }
-
-  const ::ZipArchiveHandle handle_;
-
-  DISALLOW_COPY_AND_ASSIGN(ZipFile);
-};
-
-}  // namespace android::idmap2
-
-#endif  // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index c163107..3bbe9d9 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -87,10 +87,6 @@
 }
 
 void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
-  Write8(header.GetTargetPackageId());
-  Write8(header.GetOverlayPackageId());
-  Write8(0U);  // padding
-  Write8(0U);  // padding
   Write32(header.GetTargetEntryCount());
   Write32(header.GetTargetInlineEntryCount());
   Write32(header.GetOverlayEntryCount());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
new file mode 100644
index 0000000..4f61801
--- /dev/null
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 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 "idmap2/FabricatedOverlay.h"
+
+#include <androidfw/ResourceUtils.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <utils/ByteOrder.h>
+#include <zlib.h>
+
+#include <fstream>
+
+namespace android::idmap2 {
+
+namespace {
+bool Read32(std::istream& stream, uint32_t* out) {
+  uint32_t value;
+  if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
+    *out = dtohl(value);
+    return true;
+  }
+  return false;
+}
+
+void Write32(std::ostream& stream, uint32_t value) {
+  uint32_t x = htodl(value);
+  stream.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
+}
+}  // namespace
+
+FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+                                     std::optional<uint32_t> crc_from_disk)
+    : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
+}
+
+FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
+                                    const std::string& target_package_name) {
+  package_name_ = package_name;
+  name_ = name;
+  target_package_name_ = target_package_name;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetOverlayable(const std::string& name) {
+  target_overlayable_ = name;
+  return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+    const std::string& resource_name, uint8_t data_type, uint32_t data_value) {
+  entries_.emplace_back(Entry{resource_name, data_type, data_value});
+  return *this;
+}
+
+Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
+  std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
+  for (const auto& res_entry : entries_) {
+    StringPiece package_substr;
+    StringPiece type_name;
+    StringPiece entry_name;
+    if (!android::ExtractResourceName(StringPiece(res_entry.resource_name), &package_substr,
+                                      &type_name, &entry_name)) {
+      return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
+    }
+
+    std::string package_name =
+        package_substr.empty() ? target_package_name_ : package_substr.to_string();
+    if (type_name.empty()) {
+      return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
+    }
+
+    if (entry_name.empty()) {
+      return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
+    }
+
+    auto package = entries.find(package_name);
+    if (package == entries.end()) {
+      package = entries
+                    .insert(std::make_pair<>(
+                        package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
+                    .first;
+    }
+
+    auto type = package->second.find(type_name.to_string());
+    if (type == package->second.end()) {
+      type =
+          package->second
+              .insert(std::make_pair<>(type_name.to_string(), std::map<std::string, TargetValue>()))
+              .first;
+    }
+
+    auto entry = type->second.find(entry_name.to_string());
+    if (entry == type->second.end()) {
+      entry = type->second.insert(std::make_pair<>(entry_name.to_string(), TargetValue())).first;
+    }
+
+    entry->second = TargetValue{res_entry.data_type, res_entry.data_value};
+  }
+
+  pb::FabricatedOverlay overlay_pb;
+  overlay_pb.set_package_name(package_name_);
+  overlay_pb.set_name(name_);
+  overlay_pb.set_target_package_name(target_package_name_);
+  overlay_pb.set_target_overlayable(target_overlayable_);
+
+  for (const auto& package : entries) {
+    auto package_pb = overlay_pb.add_packages();
+    package_pb->set_name(package.first);
+
+    for (const auto& type : package.second) {
+      auto type_pb = package_pb->add_types();
+      type_pb->set_name(type.first);
+
+      for (const auto& entry : type.second) {
+        auto entry_pb = type_pb->add_entries();
+        entry_pb->set_name(entry.first);
+        pb::ResourceValue* value = entry_pb->mutable_res_value();
+        value->set_data_type(entry.second.data_type);
+        value->set_data_value(entry.second.data_value);
+      }
+    }
+  }
+
+  return FabricatedOverlay(std::move(overlay_pb));
+}
+
+Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
+  uint32_t magic;
+  if (!Read32(stream, &magic)) {
+    return Error("Failed to read fabricated overlay magic.");
+  }
+
+  if (magic != kFabricatedOverlayMagic) {
+    return Error("Not a fabricated overlay file.");
+  }
+
+  uint32_t version;
+  if (!Read32(stream, &version)) {
+    return Error("Failed to read fabricated overlay version.");
+  }
+
+  if (version != 1) {
+    return Error("Invalid fabricated overlay version '%u'.", version);
+  }
+
+  uint32_t crc;
+  if (!Read32(stream, &crc)) {
+    return Error("Failed to read fabricated overlay version.");
+  }
+
+  pb::FabricatedOverlay overlay{};
+  if (!overlay.ParseFromIstream(&stream)) {
+    return Error("Failed read fabricated overlay proto.");
+  }
+
+  // If the proto version is the latest version, then the contents of the proto must be the same
+  // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
+  // proto to the latest version will likely change the contents of the fabricated overlay.
+  return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
+                                                   ? std::optional<uint32_t>(crc)
+                                                   : std::nullopt);
+}
+
+Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
+  if (!data_.has_value()) {
+    auto size = overlay_pb_.ByteSizeLong();
+    auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+
+    // Ensure serialization is deterministic
+    google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
+    google::protobuf::io::CodedOutputStream output_stream(&array_stream);
+    output_stream.SetSerializationDeterministic(true);
+    overlay_pb_.SerializeWithCachedSizes(&output_stream);
+    if (output_stream.HadError() || size != output_stream.ByteCount()) {
+      return Error("Failed to serialize fabricated overlay.");
+    }
+
+    // Calculate the crc using the proto data and the version.
+    uint32_t crc = crc32(0L, Z_NULL, 0);
+    crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
+                sizeof(uint32_t));
+    crc = crc32(crc, data.get(), size);
+    data_ = SerializedData{std::move(data), size, crc};
+  }
+  return &(*data_);
+}
+Result<uint32_t> FabricatedOverlay::GetCrc() const {
+  if (crc_from_disk_.has_value()) {
+    return *crc_from_disk_;
+  }
+  auto data = InitializeData();
+  if (!data) {
+    return data.GetError();
+  }
+  return (*data)->crc;
+}
+
+Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
+  auto data = InitializeData();
+  if (!data) {
+    return data.GetError();
+  }
+
+  Write32(stream, kFabricatedOverlayMagic);
+  Write32(stream, kFabricatedOverlayCurrentVersion);
+  Write32(stream, (*data)->crc);
+  stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
+  if (stream.bad()) {
+    return Error("Failed to write serialized fabricated overlay.");
+  }
+
+  return Unit{};
+}
+
+using FabContainer = FabricatedOverlayContainer;
+FabContainer::FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path)
+    : overlay_(std::forward<FabricatedOverlay>(overlay)), path_(std::forward<std::string>(path)) {
+}
+
+FabContainer::~FabricatedOverlayContainer() = default;
+
+Result<std::unique_ptr<FabContainer>> FabContainer::FromPath(std::string path) {
+  std::fstream fin(path);
+  auto overlay = FabricatedOverlay::FromBinaryStream(fin);
+  if (!overlay) {
+    return overlay.GetError();
+  }
+  return std::unique_ptr<FabContainer>(
+      new FabricatedOverlayContainer(std::move(*overlay), std::move(path)));
+}
+
+std::unique_ptr<FabricatedOverlayContainer> FabContainer::FromOverlay(FabricatedOverlay&& overlay) {
+  return std::unique_ptr<FabContainer>(
+      new FabricatedOverlayContainer(std::move(overlay), {} /* path */));
+}
+
+OverlayManifestInfo FabContainer::GetManifestInfo() const {
+  const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
+  return OverlayManifestInfo{
+      .package_name = overlay_pb.package_name(),
+      .name = overlay_pb.name(),
+      .target_package = overlay_pb.target_package_name(),
+      .target_name = overlay_pb.target_overlayable(),
+  };
+}
+
+Result<OverlayManifestInfo> FabContainer::FindOverlayInfo(const std::string& name) const {
+  const OverlayManifestInfo info = GetManifestInfo();
+  if (name != info.name) {
+    return Error("Failed to find name '%s' in fabricated overlay", name.c_str());
+  }
+  return info;
+}
+
+Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info) const {
+  const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
+  if (info.name != overlay_pb.name()) {
+    return Error("Failed to find name '%s' in fabricated overlay", info.name.c_str());
+  }
+
+  OverlayData result{};
+  for (const auto& package : overlay_pb.packages()) {
+    for (const auto& type : package.types()) {
+      for (const auto& entry : type.entries()) {
+        auto name = base::StringPrintf("%s:%s/%s", package.name().c_str(), type.name().c_str(),
+                                       entry.name().c_str());
+        const auto& res_value = entry.res_value();
+        result.pairs.emplace_back(OverlayData::Value{
+            name, TargetValue{.data_type = static_cast<uint8_t>(res_value.data_type()),
+                              .data_value = res_value.data_value()}});
+      }
+    }
+  }
+  return result;
+}
+
+Result<uint32_t> FabContainer::GetCrc() const {
+  return overlay_.GetCrc();
+}
+
+const std::string& FabContainer::GetPath() const {
+  return path_;
+}
+
+Result<std::string> FabContainer::GetResourceName(ResourceId /* id */) const {
+  return Error("Fabricated overlay does not contain resources.");
+}
+
+}  // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 3af1f70..98a4cea 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -47,4 +47,19 @@
 }
 #endif
 
+std::string RandomStringForPath(const size_t length) {
+  constexpr char kChars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  constexpr size_t kCharLastIndex = sizeof(kChars) - 1;
+
+  std::string out_rand;
+  out_rand.reserve(length);
+
+  std::random_device rd;
+  std::uniform_int_distribution<int> dist(0, kCharLastIndex);
+  for (size_t i = 0; i < length; i++) {
+    out_rand[i] = kChars[dist(rd) % (kCharLastIndex)];
+  }
+  return out_rand;
+}
+
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index a0cc407..6515d55 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -20,23 +20,16 @@
 #include <iostream>
 #include <iterator>
 #include <limits>
-#include <map>
 #include <memory>
-#include <set>
 #include <string>
 #include <utility>
-#include <vector>
 
 #include "android-base/macros.h"
-#include "android-base/stringprintf.h"
 #include "androidfw/AssetManager2.h"
 #include "idmap2/ResourceMapping.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "idmap2/ZipFile.h"
-#include "utils/String16.h"
-#include "utils/String8.h"
 
 namespace android::idmap2 {
 
@@ -93,22 +86,13 @@
 
 }  // namespace
 
-Result<uint32_t> GetPackageCrc(const ZipFile& zip) {
-  const Result<uint32_t> a = zip.Crc("resources.arsc");
-  const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
-  return a && b
-             ? Result<uint32_t>(*a ^ *b)
-             : Error("failed to get CRC for \"%s\"", a ? "AndroidManifest.xml" : "resources.arsc");
-}
-
 std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
   if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_)) {
     return nullptr;
   }
 
-  if (idmap_header->magic_ != kIdmapMagic ||
-      idmap_header->version_ != kIdmapCurrentVersion) {
+  if (idmap_header->magic_ != kIdmapMagic || idmap_header->version_ != kIdmapCurrentVersion) {
     // Do not continue parsing if the file is not a current version idmap.
     return nullptr;
   }
@@ -127,32 +111,22 @@
   return std::move(idmap_header);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
-                                     const std::string& overlay_path,
+Result<Unit> IdmapHeader::IsUpToDate(const TargetResourceContainer& target,
+                                     const OverlayResourceContainer& overlay,
                                      const std::string& overlay_name,
                                      PolicyBitmask fulfilled_policies,
                                      bool enforce_overlayable) const {
-  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
-  if (!target_zip) {
-    return Error("failed to open target %s", target_path.c_str());
-  }
-
-  const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
+  const Result<uint32_t> target_crc = target.GetCrc();
   if (!target_crc) {
     return Error("failed to get target crc");
   }
 
-  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
-  if (!overlay_zip) {
-    return Error("failed to overlay target %s", overlay_path.c_str());
-  }
-
-  const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
+  const Result<uint32_t> overlay_crc = overlay.GetCrc();
   if (!overlay_crc) {
     return Error("failed to get overlay crc");
   }
 
-  return IsUpToDate(target_path, overlay_path, overlay_name, *target_crc, *overlay_crc,
+  return IsUpToDate(target.GetPath(), overlay.GetPath(), overlay_name, *target_crc, *overlay_crc,
                     fulfilled_policies, enforce_overlayable);
 }
 
@@ -209,11 +183,7 @@
 
 std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
-
-  uint8_t padding;
-  if (!Read8(stream, &idmap_data_header->target_package_id_) ||
-      !Read8(stream, &idmap_data_header->overlay_package_id_) || !Read8(stream, &padding) ||
-      !Read8(stream, &padding) || !Read32(stream, &idmap_data_header->target_entry_count) ||
+  if (!Read32(stream, &idmap_data_header->target_entry_count) ||
       !Read32(stream, &idmap_data_header->target_entry_inline_count) ||
       !Read32(stream, &idmap_data_header->overlay_entry_count) ||
       !Read32(stream, &idmap_data_header->string_pool_index_offset)) {
@@ -321,8 +291,6 @@
   }
 
   std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
-  data_header->target_package_id_ = resource_mapping.GetTargetPackageId();
-  data_header->overlay_package_id_ = resource_mapping.GetOverlayPackageId();
   data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size());
   data_header->target_entry_inline_count =
       static_cast<uint32_t>(data->target_inline_entries_.size());
@@ -332,57 +300,46 @@
   return {std::move(data)};
 }
 
-Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
-                                                          const ApkAssets& overlay_apk_assets,
-                                                          const std::string& overlay_name,
-                                                          const PolicyBitmask& fulfilled_policies,
-                                                          bool enforce_overlayable) {
+Result<std::unique_ptr<const Idmap>> Idmap::FromContainers(const TargetResourceContainer& target,
+                                                           const OverlayResourceContainer& overlay,
+                                                           const std::string& overlay_name,
+                                                           const PolicyBitmask& fulfilled_policies,
+                                                           bool enforce_overlayable) {
   SYSTRACE << "Idmap::FromApkAssets";
-  const std::string& target_apk_path = target_apk_assets.GetPath();
-  const std::string& overlay_apk_path = overlay_apk_assets.GetPath();
-
-  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
-  if (!target_zip) {
-    return Error("failed to open target as zip");
-  }
-
-  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
-  if (!overlay_zip) {
-    return Error("failed to open overlay as zip");
-  }
-
   std::unique_ptr<IdmapHeader> header(new IdmapHeader());
   header->magic_ = kIdmapMagic;
   header->version_ = kIdmapCurrentVersion;
 
-  Result<uint32_t> crc = GetPackageCrc(*target_zip);
-  if (!crc) {
-    return Error(crc.GetError(), "failed to get zip CRC for target");
+  const auto target_crc = target.GetCrc();
+  if (!target_crc) {
+    return Error(target_crc.GetError(), "failed to get zip CRC for '%s'", target.GetPath().data());
   }
-  header->target_crc_ = *crc;
+  header->target_crc_ = *target_crc;
 
-  crc = GetPackageCrc(*overlay_zip);
-  if (!crc) {
-    return Error(crc.GetError(), "failed to get zip CRC for overlay");
+  const auto overlay_crc = overlay.GetCrc();
+  if (!overlay_crc) {
+    return Error(overlay_crc.GetError(), "failed to get zip CRC for '%s'",
+                 overlay.GetPath().data());
   }
-  header->overlay_crc_ = *crc;
+  header->overlay_crc_ = *overlay_crc;
+
   header->fulfilled_policies_ = fulfilled_policies;
   header->enforce_overlayable_ = enforce_overlayable;
-  header->target_path_ = target_apk_path;
-  header->overlay_path_ = overlay_apk_path;
+  header->target_path_ = target.GetPath();
+  header->overlay_path_ = overlay.GetPath();
   header->overlay_name_ = overlay_name;
 
-  auto info = utils::ExtractOverlayManifestInfo(overlay_apk_path, overlay_name);
+  auto info = overlay.FindOverlayInfo(overlay_name);
   if (!info) {
-    return info.GetError();
+    return Error(info.GetError(), "failed to get overlay info for '%s'", overlay.GetPath().data());
   }
 
   LogInfo log_info;
-  auto resource_mapping =
-      ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *info,
-                                     fulfilled_policies, enforce_overlayable, log_info);
+  auto resource_mapping = ResourceMapping::FromContainers(
+      target, overlay, *info, fulfilled_policies, enforce_overlayable, log_info);
   if (!resource_mapping) {
-    return resource_mapping.GetError();
+    return Error(resource_mapping.GetError(), "failed to generate resource map for '%s'",
+                 overlay.GetPath().data());
   }
 
   auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping);
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index 7e090a9..721612c 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -27,8 +27,6 @@
 
 namespace android::idmap2 {
 
-#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
-
 #define TAB "    "
 
 void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
@@ -36,8 +34,8 @@
 
 void PrettyPrintVisitor::visit(const IdmapHeader& header) {
   stream_ << "Paths:" << std::endl
-          << TAB "target apk path  : " << header.GetTargetPath() << std::endl
-          << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
+          << TAB "target path  : " << header.GetTargetPath() << std::endl
+          << TAB "overlay path : " << header.GetOverlayPath() << std::endl;
 
   if (!header.GetOverlayName().empty()) {
     stream_ << "Overlay name: " << header.GetOverlayName() << std::endl;
@@ -53,14 +51,11 @@
     }
   }
 
-  if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath())) {
-    target_am_.SetApkAssets({target_apk_.get()});
-    apk_assets_.push_back(std::move(target_apk_));
+  if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
+    target_ = std::move(*target);
   }
-
-  if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath())) {
-    overlay_am_.SetApkAssets({overlay_apk.get()});
-    apk_assets_.push_back(std::move(overlay_apk));
+  if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
+    overlay_ = std::move(*overlay);
   }
 
   stream_ << "Mapping:" << std::endl;
@@ -72,23 +67,20 @@
 void PrettyPrintVisitor::visit(const IdmapData& data) {
   static constexpr const char* kUnknownResourceName = "???";
 
-  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
-  const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
-
   const ResStringPool string_pool(data.GetStringPoolData().data(), data.GetStringPoolData().size());
   const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
 
   for (const auto& target_entry : data.GetTargetEntries()) {
     std::string target_name = kUnknownResourceName;
-    if (target_package_loaded) {
-      if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
+    if (target_ != nullptr) {
+      if (auto name = target_->GetResourceName(target_entry.target_id)) {
         target_name = *name;
       }
     }
 
     std::string overlay_name = kUnknownResourceName;
-    if (overlay_package_loaded) {
-      if (auto name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id)) {
+    if (overlay_ != nullptr) {
+      if (auto name = overlay_->GetResourceName(target_entry.overlay_id)) {
         overlay_name = *name;
       }
     }
@@ -112,8 +104,8 @@
     }
 
     std::string target_name = kUnknownResourceName;
-    if (target_package_loaded) {
-      if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
+    if (target_ != nullptr) {
+      if (auto name = target_->GetResourceName(target_entry.target_id)) {
         target_name = *name;
       }
     }
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index b517aa3..a016a36 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -18,16 +18,13 @@
 
 #include <algorithm>
 #include <cstdarg>
-#include <string>
 
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
-#include "androidfw/ApkAssets.h"
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 
-using android::ApkAssets;
 using android::idmap2::policy::PoliciesToDebugString;
 
 namespace android::idmap2 {
@@ -48,27 +45,19 @@
   print(header.GetOverlayName(), true /* print_value */, "overlay name");
   print(header.GetDebugInfo(), false /* print_value */, "debug info");
 
-  auto target_apk_ = ApkAssets::Load(header.GetTargetPath());
-  if (target_apk_) {
-    target_am_.SetApkAssets({target_apk_.get()});
-    apk_assets_.push_back(std::move(target_apk_));
+  if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
+    target_ = std::move(*target);
   }
-
-  auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath());
-  if (overlay_apk_) {
-    overlay_am_.SetApkAssets({overlay_apk_.get()});
-    apk_assets_.push_back(std::move(overlay_apk_));
+  if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
+    overlay_ = std::move(*overlay);
   }
 }
 
 void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
-  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
-  const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
-
   for (auto& target_entry : data.GetTargetEntries()) {
     Result<std::string> target_name(Error(""));
-    if (target_package_loaded) {
-      target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+    if (target_ != nullptr) {
+      target_name = target_->GetResourceName(target_entry.target_id);
     }
     if (target_name) {
       print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -77,8 +66,8 @@
     }
 
     Result<std::string> overlay_name(Error(""));
-    if (overlay_package_loaded) {
-      overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id);
+    if (overlay_ != nullptr) {
+      overlay_name = overlay_->GetResourceName(target_entry.overlay_id);
     }
     if (overlay_name) {
       print(target_entry.overlay_id, "overlay id: %s", overlay_name->c_str());
@@ -89,8 +78,8 @@
 
   for (auto& target_entry : data.GetTargetInlineEntries()) {
     Result<std::string> target_name(Error(""));
-    if (target_package_loaded) {
-      target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+    if (target_ != nullptr) {
+      target_name = target_->GetResourceName(target_entry.target_id);
     }
     if (target_name) {
       print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -104,10 +93,10 @@
           utils::DataTypeToString(target_entry.value.data_type).data());
 
     Result<std::string> overlay_name(Error(""));
-    if (overlay_package_loaded &&
+    if (overlay_ != nullptr &&
         (target_entry.value.data_value == Res_value::TYPE_REFERENCE ||
          target_entry.value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) {
-      overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.value.data_value);
+      overlay_name = overlay_->GetResourceName(target_entry.value.data_value);
     }
 
     if (overlay_name) {
@@ -119,8 +108,8 @@
 
   for (auto& overlay_entry : data.GetOverlayEntries()) {
     Result<std::string> overlay_name(Error(""));
-    if (overlay_package_loaded) {
-      overlay_name = utils::ResToTypeEntryName(overlay_am_, overlay_entry.overlay_id);
+    if (overlay_ != nullptr) {
+      overlay_name = overlay_->GetResourceName(overlay_entry.overlay_id);
     }
 
     if (overlay_name) {
@@ -130,8 +119,8 @@
     }
 
     Result<std::string> target_name(Error(""));
-    if (target_package_loaded) {
-      target_name = utils::ResToTypeEntryName(target_am_, overlay_entry.target_id);
+    if (target_ != nullptr) {
+      target_name = target_->GetResourceName(overlay_entry.target_id);
     }
 
     if (target_name) {
@@ -145,9 +134,6 @@
 }
 
 void RawPrintVisitor::visit(const IdmapData::Header& header) {
-  print(header.GetTargetPackageId(), "target package id");
-  print(header.GetOverlayPackageId(), "overlay package id");
-  align();
   print(header.GetTargetEntryCount(), "target entry count");
   print(header.GetTargetInlineEntryCount(), "target inline entry count");
   print(header.GetOverlayEntryCount(), "overlay entry count");
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
new file mode 100644
index 0000000..9147cca
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2021 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 "idmap2/ResourceContainer.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/Util.h"
+#include "idmap2/FabricatedOverlay.h"
+#include "idmap2/XmlParser.h"
+
+namespace android::idmap2 {
+namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
+
+#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
+
+constexpr ResourceId kAttrName = 0x01010003;
+constexpr ResourceId kAttrResourcesMap = 0x01010609;
+constexpr ResourceId kAttrTargetName = 0x0101044d;
+constexpr ResourceId kAttrTargetPackage = 0x01010021;
+
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+const LoadedPackage* GetPackageAtIndex0(const LoadedArsc* loaded_arsc) {
+  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
+  if (packages.empty()) {
+    return nullptr;
+  }
+  return loaded_arsc->GetPackageById(packages[0]->GetPackageId());
+}
+
+Result<uint32_t> CalculateCrc(const ZipAssetsProvider* zip_assets) {
+  constexpr const char* kResourcesArsc = "resources.arsc";
+  std::optional<uint32_t> res_crc = zip_assets->GetCrc(kResourcesArsc);
+  if (!res_crc) {
+    return Error("failed to get CRC for '%s'", kResourcesArsc);
+  }
+
+  constexpr const char* kManifest = "AndroidManifest.xml";
+  std::optional<uint32_t> man_crc = zip_assets->GetCrc(kManifest);
+  if (!man_crc) {
+    return Error("failed to get CRC for '%s'", kManifest);
+  }
+
+  return *res_crc ^ *man_crc;
+}
+
+Result<XmlParser> OpenXmlParser(const std::string& entry_path, const ZipAssetsProvider* zip) {
+  auto manifest = zip->Open(entry_path);
+  if (manifest == nullptr) {
+    return Error("failed to find %s ", entry_path.c_str());
+  }
+
+  auto size = manifest->getLength();
+  auto buffer = manifest->getIncFsBuffer(true /* aligned */).convert<uint8_t>();
+  if (!buffer.verify(size)) {
+    return Error("failed to read entire %s", entry_path.c_str());
+  }
+
+  return XmlParser::Create(buffer.unsafe_ptr(), size, true /* copyData */);
+}
+
+Result<XmlParser> OpenXmlParser(ResourceId id, const ZipAssetsProvider* zip,
+                                const AssetManager2* am) {
+  const auto ref_table = am->GetDynamicRefTableForCookie(0);
+  if (ref_table == nullptr) {
+    return Error("failed to find dynamic ref table for cookie 0");
+  }
+
+  ref_table->lookupResourceId(&id);
+  auto value = am->GetResource(id);
+  if (!value.has_value()) {
+    return Error("failed to find resource for id 0x%08x", id);
+  }
+
+  if (value->type != Res_value::TYPE_STRING) {
+    return Error("resource for is 0x%08x is not a file", id);
+  }
+
+  auto string_pool = am->GetStringPoolForCookie(value->cookie);
+  auto file = string_pool->string8ObjectAt(value->data);
+  if (!file.has_value()) {
+    return Error("failed to find string for index %d", value->data);
+  }
+
+  return OpenXmlParser(file->c_str(), zip);
+}
+
+Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const ZipAssetsProvider* zip,
+                                                       const std::string& name) {
+  Result<XmlParser> xml = OpenXmlParser("AndroidManifest.xml", zip);
+  if (!xml) {
+    return xml.GetError();
+  }
+
+  auto manifest_it = xml->tree_iterator();
+  if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
+    return Error("root element tag is not <manifest> in AndroidManifest.xml");
+  }
+
+  std::string package_name;
+  if (auto result_str = manifest_it->GetAttributeStringValue("package")) {
+    package_name = *result_str;
+  } else {
+    return result_str.GetError();
+  }
+
+  for (auto&& it : manifest_it) {
+    if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
+      continue;
+    }
+
+    OverlayManifestInfo info{};
+    info.package_name = package_name;
+    if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
+      if (*result_str != name) {
+        // A value for android:name was found, but either a the name does not match the requested
+        // name, or an <overlay> tag with no name was requested.
+        continue;
+      }
+      info.name = *result_str;
+    } else if (!name.empty()) {
+      // This tag does not have a value for android:name, but an <overlay> tag with a specific name
+      // has been requested.
+      continue;
+    }
+
+    if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
+      info.target_package = *result_str;
+    } else {
+      return Error("android:targetPackage missing from <overlay> in AndroidManifest.xml");
+    }
+
+    if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
+      info.target_name = *result_str;
+    }
+
+    if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
+      if (utils::IsReference((*result_value).dataType)) {
+        info.resource_mapping = (*result_value).data;
+      } else {
+        return Error("android:resourcesMap is not a reference in AndroidManifest.xml");
+      }
+    }
+    return info;
+  }
+
+  return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml", name.c_str());
+}
+
+Result<OverlayData> CreateResourceMapping(ResourceId id, const ZipAssetsProvider* zip,
+                                          const AssetManager2* overlay_am,
+                                          const LoadedArsc* overlay_arsc,
+                                          const LoadedPackage* overlay_package) {
+  auto parser = OpenXmlParser(id, zip, overlay_am);
+  if (!parser) {
+    return parser.GetError();
+  }
+
+  OverlayData overlay_data{};
+  const uint32_t string_pool_offset = overlay_arsc->GetStringPool()->size();
+  const uint8_t package_id = overlay_package->GetPackageId();
+  auto root_it = parser->tree_iterator();
+  if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
+    return Error("root element is not <overlay> tag");
+  }
+
+  auto overlay_it_end = root_it.end();
+  for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
+    if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
+      return Error("failed to parse overlay xml document");
+    }
+
+    if (overlay_it->event() != XmlParser::Event::START_TAG) {
+      continue;
+    }
+
+    if (overlay_it->name() != "item") {
+      return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+    }
+
+    Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
+    if (!target_resource) {
+      return Error(R"(<item> tag missing expected attribute "target")");
+    }
+
+    Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
+    if (!overlay_resource) {
+      return Error(R"(<item> tag missing expected attribute "value")");
+    }
+
+    if (overlay_resource->dataType == Res_value::TYPE_STRING) {
+      overlay_resource->data += string_pool_offset;
+    }
+
+    if (utils::IsReference(overlay_resource->dataType)) {
+      // Only rewrite resources defined within the overlay package to their corresponding target
+      // resource ids at runtime.
+      bool rewrite_id = package_id == EXTRACT_PACKAGE(overlay_resource->data);
+      overlay_data.pairs.emplace_back(OverlayData::Value{
+          *target_resource, OverlayData::ResourceIdValue{overlay_resource->data, rewrite_id}});
+    } else {
+      overlay_data.pairs.emplace_back(
+          OverlayData::Value{*target_resource, TargetValue{.data_type = overlay_resource->dataType,
+                                                           .data_value = overlay_resource->data}});
+    }
+  }
+
+  const auto& string_pool = parser->get_strings();
+  const uint32_t string_pool_data_length = string_pool.bytes();
+  overlay_data.string_pool_data = OverlayData::InlineStringPoolData{
+      .data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
+      .data_length = string_pool_data_length,
+      .string_pool_offset = string_pool_offset,
+  };
+
+  // Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
+  memcpy(overlay_data.string_pool_data->data.get(), string_pool.data().unsafe_ptr(),
+         string_pool_data_length);
+  return overlay_data;
+}
+
+OverlayData CreateResourceMappingLegacy(const AssetManager2* overlay_am,
+                                        const LoadedPackage* overlay_package) {
+  OverlayData overlay_data{};
+  for (const ResourceId overlay_resid : *overlay_package) {
+    if (auto name = utils::ResToTypeEntryName(*overlay_am, overlay_resid)) {
+      // Disable rewriting. Overlays did not support internal references before
+      // android:resourcesMap. Do not introduce new behavior.
+      overlay_data.pairs.emplace_back(OverlayData::Value{
+          *name, OverlayData::ResourceIdValue{overlay_resid, false /* rewrite_id */}});
+    }
+  }
+  return overlay_data;
+}
+
+struct ResState {
+  std::unique_ptr<ApkAssets> apk_assets;
+  const LoadedArsc* arsc;
+  const LoadedPackage* package;
+  std::unique_ptr<AssetManager2> am;
+  ZipAssetsProvider* zip_assets;
+
+  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+    ResState state;
+    state.zip_assets = zip.get();
+    if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+      return Error("failed to load apk asset");
+    }
+
+    if ((state.arsc = state.apk_assets->GetLoadedArsc()) == nullptr) {
+      return Error("failed to retrieve loaded arsc");
+    }
+
+    if ((state.package = GetPackageAtIndex0(state.arsc)) == nullptr) {
+      return Error("failed to retrieve loaded package at index 0");
+    }
+
+    state.am = std::make_unique<AssetManager2>();
+    if (!state.am->SetApkAssets({state.apk_assets.get()})) {
+      return Error("failed to create asset manager");
+    }
+
+    return state;
+  }
+};
+
+}  // namespace
+
+struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
+  static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
+
+  // inherited from TargetResourceContainer
+  Result<bool> DefinesOverlayable() const override;
+  Result<const android::OverlayableInfo*> GetOverlayableInfo(ResourceId id) const override;
+  Result<ResourceId> GetResourceId(const std::string& name) const override;
+
+  // inherited from OverlayResourceContainer
+  Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
+  Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
+
+  // inherited from ResourceContainer
+  Result<uint32_t> GetCrc() const override;
+  Result<std::string> GetResourceName(ResourceId id) const override;
+  const std::string& GetPath() const override;
+
+  ~ApkResourceContainer() override = default;
+
+ private:
+  ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets, std::string path);
+
+  Result<const ResState*> GetState() const;
+  ZipAssetsProvider* GetZipAssets() const;
+
+  mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
+  std::string path_;
+};
+
+ApkResourceContainer::ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets,
+                                           std::string path)
+    : state_(std::move(zip_assets)), path_(std::move(path)) {
+}
+
+Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
+    const std::string& path) {
+  auto zip_assets = ZipAssetsProvider::Create(path);
+  if (zip_assets == nullptr) {
+    return Error("failed to load zip assets");
+  }
+  return std::unique_ptr<ApkResourceContainer>(
+      new ApkResourceContainer(std::move(zip_assets), path));
+}
+
+Result<const ResState*> ApkResourceContainer::GetState() const {
+  if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
+    return state;
+  }
+
+  auto state =
+      ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+  if (!state) {
+    return state.GetError();
+  }
+
+  state_ = std::move(*state);
+  return &std::get<ResState>(state_);
+}
+
+ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
+  if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
+    return zip->get();
+  }
+  return std::get<ResState>(state_).zip_assets;
+}
+
+Result<bool> ApkResourceContainer::DefinesOverlayable() const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  return (*state)->package->DefinesOverlayable();
+}
+
+Result<const android::OverlayableInfo*> ApkResourceContainer::GetOverlayableInfo(
+    ResourceId id) const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  return (*state)->package->GetOverlayableInfo(id);
+}
+
+Result<OverlayManifestInfo> ApkResourceContainer::FindOverlayInfo(const std::string& name) const {
+  return ExtractOverlayManifestInfo(GetZipAssets(), name);
+}
+
+Result<OverlayData> ApkResourceContainer::GetOverlayData(const OverlayManifestInfo& info) const {
+  const auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+
+  if (info.resource_mapping != 0) {
+    return CreateResourceMapping(info.resource_mapping, GetZipAssets(), (*state)->am.get(),
+                                 (*state)->arsc, (*state)->package);
+  }
+  return CreateResourceMappingLegacy((*state)->am.get(), (*state)->package);
+}
+
+Result<uint32_t> ApkResourceContainer::GetCrc() const {
+  return CalculateCrc(GetZipAssets());
+}
+
+const std::string& ApkResourceContainer::GetPath() const {
+  return path_;
+}
+
+Result<ResourceId> ApkResourceContainer::GetResourceId(const std::string& name) const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  auto id = (*state)->am->GetResourceId(name, "", (*state)->package->GetPackageName());
+  if (!id.has_value()) {
+    return Error("failed to find resource '%s'", name.c_str());
+  }
+
+  // Retrieve the compile-time resource id of the target resource.
+  return REWRITE_PACKAGE(*id, (*state)->package->GetPackageId());
+}
+
+Result<std::string> ApkResourceContainer::GetResourceName(ResourceId id) const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  return utils::ResToTypeEntryName(*(*state)->am, id);
+}
+
+Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
+    std::string path) {
+  auto result = ApkResourceContainer::FromPath(path);
+  if (!result) {
+    return result.GetError();
+  }
+  return std::unique_ptr<TargetResourceContainer>(result->release());
+}
+
+Result<std::unique_ptr<OverlayResourceContainer>> OverlayResourceContainer::FromPath(
+    std::string path) {
+  // Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
+  if (android::IsFabricatedOverlay(path)) {
+    auto result = FabricatedOverlayContainer::FromPath(path);
+    if (!result) {
+      return result.GetError();
+    }
+    return std::unique_ptr<OverlayResourceContainer>(result->release());
+  }
+
+  // Fallback to loading the container as an APK.
+  auto result = ApkResourceContainer::FromPath(path);
+  if (!result) {
+    return result.GetError();
+  }
+  return std::unique_ptr<OverlayResourceContainer>(result->release());
+}
+
+}  // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 46eeb8e..3bbbf24 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -30,19 +30,12 @@
 
 using android::base::StringPrintf;
 using android::idmap2::utils::BitmaskToPolicies;
-using android::idmap2::utils::IsReference;
-using android::idmap2::utils::ResToTypeEntryName;
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
 namespace {
-
-#define REWRITE_PACKAGE(resid, package_id) \
-  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
-#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
-
 std::string ConcatPolicies(const std::vector<std::string>& policies) {
   std::string message;
   for (const std::string& policy : policies) {
@@ -55,11 +48,11 @@
   return message;
 }
 
-Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
+Result<Unit> CheckOverlayable(const TargetResourceContainer& target,
                               const OverlayManifestInfo& overlay_info,
                               const PolicyBitmask& fulfilled_policies,
                               const ResourceId& target_resource) {
-  static constexpr const PolicyBitmask sDefaultPolicies =
+  constexpr const PolicyBitmask kDefaultPolicies =
       PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
       PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE |
       PolicyFlags::CONFIG_SIGNATURE;
@@ -68,8 +61,13 @@
   // the overlay is preinstalled, signed with the same signature as the target or signed with the
   // same signature as reference package defined in SystemConfig under 'overlay-config-signature'
   // tag.
-  if (!target_package.DefinesOverlayable()) {
-    return (sDefaultPolicies & fulfilled_policies) != 0
+  const Result<bool> defines_overlayable = target.DefinesOverlayable();
+  if (!defines_overlayable) {
+    return Error(defines_overlayable.GetError(), "unable to retrieve overlayable info");
+  }
+
+  if (!*defines_overlayable) {
+    return (kDefaultPolicies & fulfilled_policies) != 0
                ? Result<Unit>({})
                : Error(
                      "overlay must be preinstalled, signed with the same signature as the target,"
@@ -77,367 +75,112 @@
                      " <overlay-config-signature>.");
   }
 
-  const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource);
-  if (overlayable_info == nullptr) {
+  const auto overlayable_info = target.GetOverlayableInfo(target_resource);
+  if (!overlayable_info) {
+    return overlayable_info.GetError();
+  }
+
+  if (*overlayable_info == nullptr) {
     // Do not allow non-overlayable resources to be overlaid.
     return Error("target resource has no overlayable declaration");
   }
 
-  if (overlay_info.target_name != overlayable_info->name) {
+  if (overlay_info.target_name != (*overlayable_info)->name) {
     // If the overlay supplies a target overlayable name, the resource must belong to the
     // overlayable defined with the specified name to be overlaid.
     return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
-                 overlay_info.target_name.c_str(), overlayable_info->name.c_str());
+                 overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
   }
 
   // Enforce policy restrictions if the resource is declared as overlayable.
-  if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
+  if (((*overlayable_info)->policy_flags & fulfilled_policies) == 0) {
     return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")",
                  ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
-                 ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
+                 ConcatPolicies(BitmaskToPolicies((*overlayable_info)->policy_flags)).c_str());
   }
 
   return Result<Unit>({});
 }
 
-// TODO(martenkongstad): scan for package name instead of assuming package at index 0
-//
-// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
-// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
-// this assumption tends to work out. That said, the correct thing to do is to scan
-// resources.arsc for a package with a given name as read from the package manifest instead of
-// relying on a hard-coded index. This however requires storing the package name in the idmap
-// header, which in turn requires incrementing the idmap version. Because the initial version of
-// idmap2 is compatible with idmap, this will have to wait for now.
-const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
-  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
-  if (packages.empty()) {
-    return nullptr;
+std::string GetDebugResourceName(const ResourceContainer& container, ResourceId resid) {
+  auto name = container.GetResourceName(resid);
+  if (name) {
+    return *name;
   }
-  int id = packages[0]->GetPackageId();
-  return loaded_arsc.GetPackageById(id);
+  return StringPrintf("0x%08x", resid);
 }
-
-Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id,
-                                                        const AssetManager2& asset_manager) {
-  auto value = asset_manager.GetResource(resource_id);
-  if (!value.has_value()) {
-    return Error("failed to find resource for id 0x%08x", resource_id);
-  }
-
-  if (value->type != Res_value::TYPE_STRING) {
-    return Error("resource for is 0x%08x is not a file", resource_id);
-  }
-
-  auto string_pool = asset_manager.GetStringPoolForCookie(value->cookie);
-  auto file = string_pool->string8ObjectAt(value->data);
-  if (!file.has_value()) {
-    return Error("failed to find string for index %d", value->data);
-  }
-
-  // Load the overlay resource mappings from the file specified using android:resourcesMap.
-  auto asset = asset_manager.OpenNonAsset(file->c_str(), Asset::AccessMode::ACCESS_BUFFER);
-  if (asset == nullptr) {
-    return Error("file \"%s\" not found", file->c_str());
-  }
-
-  return asset;
-}
-
 }  // namespace
 
-Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am,
-                                                               const LoadedPackage* target_package,
-                                                               const LoadedPackage* overlay_package,
-                                                               size_t string_pool_offset,
-                                                               const XmlParser& overlay_parser,
-                                                               LogInfo& log_info) {
-  ResourceMapping resource_mapping;
-  auto root_it = overlay_parser.tree_iterator();
-  if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
-    return Error("root element is not <overlay> tag");
+Result<ResourceMapping> ResourceMapping::FromContainers(const TargetResourceContainer& target,
+                                                        const OverlayResourceContainer& overlay,
+                                                        const OverlayManifestInfo& overlay_info,
+                                                        const PolicyBitmask& fulfilled_policies,
+                                                        bool enforce_overlayable,
+                                                        LogInfo& log_info) {
+  auto overlay_data = overlay.GetOverlayData(overlay_info);
+  if (!overlay_data) {
+    return overlay_data.GetError();
   }
 
-  const uint8_t target_package_id = target_package->GetPackageId();
-  const uint8_t overlay_package_id = overlay_package->GetPackageId();
-  auto overlay_it_end = root_it.end();
-  for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
-    if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
-      return Error("failed to parse overlay xml document");
-    }
-
-    if (overlay_it->event() != XmlParser::Event::START_TAG) {
+  ResourceMapping mapping;
+  for (const auto& overlay_pair : overlay_data->pairs) {
+    const auto target_resid = target.GetResourceId(overlay_pair.resource_name);
+    if (!target_resid) {
+      log_info.Warning(LogMessage() << target_resid.GetErrorMessage());
       continue;
     }
 
-    if (overlay_it->name() != "item") {
-      return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+    if (enforce_overlayable) {
+      // Filter out resources the overlay is not allowed to override.
+      auto overlayable = CheckOverlayable(target, overlay_info, fulfilled_policies, *target_resid);
+      if (!overlayable) {
+        log_info.Warning(LogMessage() << "overlay '" << overlay.GetPath()
+                                      << "' is not allowed to overlay resource '"
+                                      << GetDebugResourceName(target, *target_resid)
+                                      << "' in target: " << overlayable.GetErrorMessage());
+        continue;
+      }
     }
 
-    Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
-    if (!target_resource) {
-      return Error(R"(<item> tag missing expected attribute "target")");
-    }
-
-    Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
-    if (!overlay_resource) {
-      return Error(R"(<item> tag missing expected attribute "value")");
-    }
-
-    auto target_id_result =
-        target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
-    if (!target_id_result.has_value()) {
-      log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource
-                                    << "\" in target resources");
-      continue;
-    }
-
-    // Retrieve the compile-time resource id of the target resource.
-    uint32_t target_id = REWRITE_PACKAGE(*target_id_result, target_package_id);
-
-    if (overlay_resource->dataType == Res_value::TYPE_STRING) {
-      overlay_resource->data += string_pool_offset;
-    }
-
-    if (IsReference(overlay_resource->dataType)) {
-      // Only rewrite resources defined within the overlay package to their corresponding target
-      // resource ids at runtime.
-      bool rewrite_reference = overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data);
-      resource_mapping.AddMapping(target_id, overlay_resource->data, rewrite_reference);
-    } else {
-      resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data);
+    if (auto result = mapping.AddMapping(*target_resid, overlay_pair.value); !result) {
+      return Error(result.GetError(), "failed to add mapping for '%s'",
+                   GetDebugResourceName(target, *target_resid).c_str());
     }
   }
 
-  return resource_mapping;
+  auto& string_pool_data = overlay_data->string_pool_data;
+  if (string_pool_data.has_value()) {
+    mapping.string_pool_offset_ = string_pool_data->string_pool_offset;
+    mapping.string_pool_data_ = std::move(string_pool_data->data);
+    mapping.string_pool_data_length_ = string_pool_data->data_length;
+  }
+
+  return std::move(mapping);
 }
 
-Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
-    const AssetManager2* target_am, const AssetManager2* overlay_am,
-    const LoadedPackage* target_package, const LoadedPackage* overlay_package, LogInfo& log_info) {
-  ResourceMapping resource_mapping;
-  const uint8_t target_package_id = target_package->GetPackageId();
-  const auto end = overlay_package->end();
-  for (auto iter = overlay_package->begin(); iter != end; ++iter) {
-    const ResourceId overlay_resid = *iter;
-    Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid);
-    if (!name) {
-      continue;
+Result<Unit> ResourceMapping::AddMapping(
+    ResourceId target_resource,
+    const std::variant<OverlayData::ResourceIdValue, TargetValue>& value) {
+  if (target_map_.find(target_resource) != target_map_.end()) {
+    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+  }
+
+  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
+  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
+
+  if (auto overlay_resource = std::get_if<OverlayData::ResourceIdValue>(&value)) {
+    target_map_.insert(std::make_pair(target_resource, overlay_resource->overlay_id));
+    if (overlay_resource->rewrite_id) {
+      // An overlay resource can override multiple target resources at once. Rewrite the overlay
+      // resource as the first target resource it overrides.
+      overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource));
     }
-
-    // Find the resource with the same type and entry name within the target package.
-    const std::string full_name =
-        base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
-    auto target_resource_result = target_am->GetResourceId(full_name);
-    if (!target_resource_result.has_value()) {
-      log_info.Warning(LogMessage()
-                       << "failed to find resource \"" << full_name << "\" in target resources");
-      continue;
-    }
-
-    // Retrieve the compile-time resource id of the target resource.
-    ResourceId target_resource = REWRITE_PACKAGE(*target_resource_result, target_package_id);
-    resource_mapping.AddMapping(target_resource, overlay_resid,
-                                false /* rewrite_overlay_reference */);
-  }
-
-  return resource_mapping;
-}
-
-void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am,
-                                                 const LoadedPackage* target_package,
-                                                 const LoadedPackage* overlay_package,
-                                                 const OverlayManifestInfo& overlay_info,
-                                                 const PolicyBitmask& fulfilled_policies,
-                                                 LogInfo& log_info) {
-  std::set<ResourceId> remove_ids;
-  for (const auto& target_map : target_map_) {
-    const ResourceId target_resid = target_map.first;
-    Result<Unit> success =
-        CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid);
-    if (success) {
-      continue;
-    }
-
-    // Attempting to overlay a resource that is not allowed to be overlaid is treated as a
-    // warning.
-    Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid);
-    if (!name) {
-      name = StringPrintf("0x%08x", target_resid);
-    }
-
-    log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName()
-                                  << "\" is not allowed to overlay resource \"" << *name
-                                  << "\" in target: " << success.GetErrorMessage());
-
-    remove_ids.insert(target_resid);
-  }
-
-  for (const ResourceId target_resid : remove_ids) {
-    RemoveMapping(target_resid);
-  }
-}
-
-Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets,
-                                                       const ApkAssets& overlay_apk_assets,
-                                                       const OverlayManifestInfo& overlay_info,
-                                                       const PolicyBitmask& fulfilled_policies,
-                                                       bool enforce_overlayable,
-                                                       LogInfo& log_info) {
-  AssetManager2 target_asset_manager;
-  if (!target_asset_manager.SetApkAssets({&target_apk_assets})) {
-    return Error("failed to create target asset manager");
-  }
-
-  AssetManager2 overlay_asset_manager;
-  if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets})) {
-    return Error("failed to create overlay asset manager");
-  }
-
-  const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
-  if (target_arsc == nullptr) {
-    return Error("failed to load target resources.arsc");
-  }
-
-  const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
-  if (overlay_arsc == nullptr) {
-    return Error("failed to load overlay resources.arsc");
-  }
-
-  const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
-  if (target_pkg == nullptr) {
-    return Error("failed to load target package from resources.arsc");
-  }
-
-  const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
-  if (overlay_pkg == nullptr) {
-    return Error("failed to load overlay package from resources.arsc");
-  }
-
-  size_t string_pool_data_length = 0U;
-  size_t string_pool_offset = 0U;
-  std::unique_ptr<uint8_t[]> string_pool_data;
-  Result<ResourceMapping> resource_mapping = {{}};
-  if (overlay_info.resource_mapping != 0U) {
-    // Use the dynamic reference table to find the assigned resource id of the map xml.
-    const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0);
-    uint32_t resource_mapping_id = overlay_info.resource_mapping;
-    ref_table->lookupResourceId(&resource_mapping_id);
-
-    // Load the overlay resource mappings from the file specified using android:resourcesMap.
-    auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager);
-    if (!asset) {
-      return Error("failed opening xml for android:resourcesMap: %s",
-                   asset.GetErrorMessage().c_str());
-    }
-
-    auto parser =
-        XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength());
-    if (!parser) {
-      return Error("failed opening ResXMLTree");
-    }
-
-    // Copy the xml string pool data before the parse goes out of scope.
-    auto& string_pool = (*parser)->get_strings();
-    string_pool_data_length = string_pool.bytes();
-    string_pool_data.reset(new uint8_t[string_pool_data_length]);
-
-    // Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
-    memcpy(string_pool_data.get(), string_pool.data().unsafe_ptr(), string_pool_data_length);
-
-    // Offset string indices by the size of the overlay resource table string pool.
-    string_pool_offset = overlay_arsc->GetStringPool()->size();
-
-    resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
-                                             string_pool_offset, *(*parser), log_info);
   } else {
-    // If no file is specified using android:resourcesMap, it is assumed that the overlay only
-    // defines resources intended to override target resources of the same type and name.
-    resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager,
-                                                   target_pkg, overlay_pkg, log_info);
+    auto overlay_value = std::get<TargetValue>(value);
+    target_map_.insert(std::make_pair(target_resource, overlay_value));
   }
 
-  if (!resource_mapping) {
-    return resource_mapping.GetError();
-  }
-
-  if (enforce_overlayable) {
-    // Filter out resources the overlay is not allowed to override.
-    (*resource_mapping)
-        .FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
-                                    fulfilled_policies, log_info);
-  }
-
-  resource_mapping->target_package_id_ = target_pkg->GetPackageId();
-  resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId();
-  resource_mapping->string_pool_offset_ = string_pool_offset;
-  resource_mapping->string_pool_data_ = std::move(string_pool_data);
-  resource_mapping->string_pool_data_length_ = string_pool_data_length;
-  return std::move(*resource_mapping);
-}
-
-OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const {
-  // An overlay resource can override multiple target resources at once. Rewrite the overlay
-  // resource as the first target resource it overrides.
-  OverlayResourceMap map;
-  for (const auto& mappings : overlay_map_) {
-    map.insert(std::make_pair(mappings.first, mappings.second));
-  }
-  return map;
-}
-
-Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, ResourceId overlay_resource,
-                                         bool rewrite_overlay_reference) {
-  if (target_map_.find(target_resource) != target_map_.end()) {
-    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
-  }
-
-  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
-  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
-
-  target_map_.insert(std::make_pair(target_resource, overlay_resource));
-
-  if (rewrite_overlay_reference) {
-    overlay_map_.insert(std::make_pair(overlay_resource, target_resource));
-  }
   return Unit{};
 }
 
-Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource,
-                                         TargetValue::DataType data_type,
-                                         TargetValue::DataValue data_value) {
-  if (target_map_.find(target_resource) != target_map_.end()) {
-    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
-  }
-
-  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
-  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
-
-  target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
-  return Unit{};
-}
-
-void ResourceMapping::RemoveMapping(ResourceId target_resource) {
-  auto target_iter = target_map_.find(target_resource);
-  if (target_iter == target_map_.end()) {
-    return;
-  }
-
-  const auto value = target_iter->second;
-  target_map_.erase(target_iter);
-
-  const ResourceId* overlay_resource = std::get_if<ResourceId>(&value);
-  if (overlay_resource == nullptr) {
-    return;
-  }
-
-  auto overlay_iter = overlay_map_.equal_range(*overlay_resource);
-  for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) {
-    if (i->second == target_resource) {
-      overlay_map_.erase(i);
-      return;
-    }
-  }
-}
-
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index 4e85e57..e809bf1 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -17,27 +17,16 @@
 #include "idmap2/ResourceUtils.h"
 
 #include <memory>
-#include <string>
 
 #include "androidfw/StringPiece.h"
 #include "androidfw/Util.h"
 #include "idmap2/Result.h"
-#include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
 
 using android::StringPiece16;
 using android::idmap2::Result;
-using android::idmap2::XmlParser;
-using android::idmap2::ZipFile;
 using android::util::Utf16ToUtf8;
 
 namespace android::idmap2::utils {
-namespace {
-constexpr ResourceId kAttrName = 0x01010003;
-constexpr ResourceId kAttrResourcesMap = 0x01010609;
-constexpr ResourceId kAttrTargetName = 0x0101044d;
-constexpr ResourceId kAttrTargetPackage = 0x01010021;
-}  // namespace
 
 bool IsReference(uint8_t data_type) {
   return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
@@ -97,71 +86,4 @@
   return out;
 }
 
-Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
-                                                       const std::string& name) {
-  std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
-  if (!zip) {
-    return Error("failed to open %s as a zip file", path.c_str());
-  }
-
-  std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml");
-  if (!entry) {
-    return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str());
-  }
-
-  Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size);
-  if (!xml) {
-    return Error("failed to parse AndroidManifest.xml from %s", path.c_str());
-  }
-
-  auto manifest_it = (*xml)->tree_iterator();
-  if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
-    return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
-  }
-
-  for (auto&& it : manifest_it) {
-    if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
-      continue;
-    }
-
-    OverlayManifestInfo info{};
-    if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
-      if (*result_str != name) {
-        // A value for android:name was found, but either a the name does not match the requested
-        // name, or an <overlay> tag with no name was requested.
-        continue;
-      }
-      info.name = *result_str;
-    } else if (!name.empty()) {
-      // This tag does not have a value for android:name, but an <overlay> tag with a specific name
-      // has been requested.
-      continue;
-    }
-
-    if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
-      info.target_package = *result_str;
-    } else {
-      return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
-                   result_str.GetErrorMessage().c_str());
-    }
-
-    if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
-      info.target_name = *result_str;
-    }
-
-    if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
-      if (IsReference((*result_value).dataType)) {
-        info.resource_mapping = (*result_value).data;
-      } else {
-        return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
-                     path.c_str());
-      }
-    }
-    return info;
-  }
-
-  return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml of %s",
-               name.c_str(), path.c_str());
-}
-
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 00baea4..70822c8 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -151,16 +151,18 @@
   return value ? GetStringValue(parser_, *value, name) : value.GetError();
 }
 
-Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
-                                                           bool copy_data) {
-  auto parser = std::unique_ptr<const XmlParser>(new XmlParser());
-  if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) {
+XmlParser::XmlParser(std::unique_ptr<ResXMLTree> tree) : tree_(std::move(tree)) {
+}
+
+Result<XmlParser> XmlParser::Create(const void* data, size_t size, bool copy_data) {
+  auto tree = std::make_unique<ResXMLTree>();
+  if (tree->setTo(data, size, copy_data) != NO_ERROR) {
     return Error("Malformed xml block");
   }
 
   // Find the beginning of the first tag.
   XmlParser::Event event;
-  while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+  while ((event = tree->next()) != XmlParser::Event::BAD_DOCUMENT &&
          event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) {
   }
 
@@ -172,11 +174,7 @@
     return Error("Bad xml document");
   }
 
-  return parser;
-}
-
-XmlParser::~XmlParser() {
-  tree_.uninit();
+  return XmlParser{std::move(tree)};
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp
deleted file mode 100644
index 1e1a218..0000000
--- a/cmds/idmap2/libidmap2/ZipFile.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "idmap2/ZipFile.h"
-
-#include <memory>
-#include <string>
-
-#include "idmap2/Result.h"
-
-namespace android::idmap2 {
-
-std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) {
-  void* ptr = ::operator new(sizeof(MemoryChunk) + size);
-  std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr));
-  chunk->size = size;
-  return chunk;
-}
-
-std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) {
-  ::ZipArchiveHandle handle;
-  int32_t status = ::OpenArchive(path.c_str(), &handle);
-  if (status != 0) {
-    ::CloseArchive(handle);
-    return nullptr;
-  }
-  return std::unique_ptr<ZipFile>(new ZipFile(handle));
-}
-
-ZipFile::~ZipFile() {
-  ::CloseArchive(handle_);
-}
-
-std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const {
-  ::ZipEntry entry;
-  int32_t status = ::FindEntry(handle_, entryPath, &entry);
-  if (status != 0) {
-    return nullptr;
-  }
-  std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length);
-  status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size);
-  if (status != 0) {
-    return nullptr;
-  }
-  return chunk;
-}
-
-Result<uint32_t> ZipFile::Crc(const std::string& entryPath) const {
-  ::ZipEntry entry;
-  int32_t status = ::FindEntry(handle_, entryPath, &entry);
-  if (status != 0) {
-    return Error("failed to find zip entry %s", entryPath.c_str());
-  }
-  return entry.crc32;
-}
-
-}  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/proto/fabricated_v1.proto b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
new file mode 100644
index 0000000..a392b2b
--- /dev/null
+++ b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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 = "proto3";
+
+package android.idmap2.pb;
+
+option optimize_for = LITE_RUNTIME;
+
+// All changes to the proto messages in this file MUST be backwards compatible. Backwards
+// incompatible changes will cause previously fabricated overlays to be considered corrupt by the
+// new proto message specification.
+message FabricatedOverlay {
+  repeated ResourcePackage packages = 1;
+  string name = 2;
+  string package_name = 3;
+  string target_package_name = 4;
+  string target_overlayable = 5;
+}
+
+message ResourcePackage {
+  string name = 1;
+  repeated ResourceType types = 2;
+}
+
+message ResourceType {
+  string name = 1;
+  repeated ResourceEntry entries = 2;
+}
+
+message ResourceEntry {
+  string name = 1;
+  oneof value {
+    ResourceValue res_value = 2;
+  }
+}
+
+message ResourceValue {
+  // Corresponds with android::Res_value::dataType
+  uint32 data_type = 1;
+  // Corresponds with android::Res_value::data
+  uint32 data_value = 2;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index 524aabc..bf63327 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -33,7 +33,7 @@
 namespace android::idmap2 {
 
 TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   auto result1 = Idmap::FromBinaryStream(raw_stream);
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
new file mode 100644
index 0000000..79ab243
--- /dev/null
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 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 <android-base/file.h>
+#include <gtest/gtest.h>
+#include <idmap2/FabricatedOverlay.h>
+
+#include <fstream>
+
+namespace android::idmap2 {
+
+TEST(FabricatedOverlayTests, OverlayInfo) {
+  auto overlay =
+      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+          .SetOverlayable("TestResources")
+          .Build();
+
+  ASSERT_TRUE(overlay);
+  auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
+  auto info = container->FindOverlayInfo("SandTheme");
+  ASSERT_TRUE(info);
+  EXPECT_EQ("SandTheme", (*info).name);
+  EXPECT_EQ("TestResources", (*info).target_name);
+
+  info = container->FindOverlayInfo("OceanTheme");
+  ASSERT_FALSE(info);
+}
+
+TEST(FabricatedOverlayTests, SetResourceValue) {
+  auto overlay =
+      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+          .SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
+          .SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
+          .Build();
+  ASSERT_TRUE(overlay);
+  auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
+  auto info = container->FindOverlayInfo("SandTheme");
+  ASSERT_TRUE(info);
+  ASSERT_TRUE((*info).target_name.empty());
+
+  auto crc = (*container).GetCrc();
+  ASSERT_TRUE(crc) << crc.GetErrorMessage();
+  EXPECT_NE(0U, *crc);
+
+  auto pairs = container->GetOverlayData(*info);
+  ASSERT_TRUE(pairs);
+  EXPECT_FALSE(pairs->string_pool_data.has_value());
+  ASSERT_EQ(3U, pairs->pairs.size());
+
+  auto& it = pairs->pairs[0];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  auto entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(1U, entry->data_value);
+  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+
+  it = pairs->pairs[1];
+  ASSERT_EQ("com.example.target:string/int3", it.resource_name);
+  entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(0x7f010000, entry->data_value);
+  ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
+
+  it = pairs->pairs[2];
+  ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
+  entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(2U, entry->data_value);
+  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+}
+
+TEST(FabricatedOverlayTests, SetResourceValueBadArgs) {
+  {
+    auto builder =
+        FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+            .SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U);
+    ASSERT_FALSE(builder.Build());
+  }
+  {
+    auto builder =
+        FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+            .SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U);
+    ASSERT_FALSE(builder.Build());
+  }
+}
+
+TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
+  auto overlay =
+      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+          .SetOverlayable("TestResources")
+          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+          .Build();
+  ASSERT_TRUE(overlay);
+  TemporaryFile tf;
+  std::ofstream out(tf.path);
+  ASSERT_TRUE((*overlay).ToBinaryStream(out));
+  out.close();
+
+  auto container = OverlayResourceContainer::FromPath(tf.path);
+  ASSERT_TRUE(container) << container.GetErrorMessage();
+  EXPECT_EQ(tf.path, (*container)->GetPath());
+
+  auto crc = (*container)->GetCrc();
+  ASSERT_TRUE(crc) << crc.GetErrorMessage();
+  EXPECT_NE(0U, *crc);
+
+  auto info = (*container)->FindOverlayInfo("SandTheme");
+  ASSERT_TRUE(info) << info.GetErrorMessage();
+  EXPECT_EQ("SandTheme", (*info).name);
+  EXPECT_EQ("TestResources", (*info).target_name);
+
+  auto pairs = (*container)->GetOverlayData(*info);
+  ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
+  EXPECT_EQ(1U, pairs->pairs.size());
+
+  auto& it = pairs->pairs[0];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  auto entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  EXPECT_EQ(1U, entry->data_value);
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+}
+
+}  // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 16b68f0..9516ff8 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <android-base/file.h>
+
 #include <cstdio>  // fclose
 #include <fstream>
 #include <memory>
@@ -61,12 +63,12 @@
 }
 
 TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream stream(raw);
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
   ASSERT_EQ(header->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(header->GetVersion(), 0x07U);
+  ASSERT_EQ(header->GetVersion(), 0x08U);
   ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
   ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
@@ -81,7 +83,7 @@
   std::stringstream stream;
   stream << android::kIdmapMagic;
   stream << 0xffffffffU;
-  stream << std::string(kJunkSize, (char) 0xffU);
+  stream << std::string(kJunkSize, (char)0xffU);
   ASSERT_FALSE(Idmap::FromBinaryStream(stream));
 }
 
@@ -90,14 +92,13 @@
   std::stringstream stream;
   stream << 0xffffffffU;
   stream << android::kIdmapCurrentVersion;
-  stream << std::string(kJunkSize, (char) 0xffU);
+  stream << std::string(kJunkSize, (char)0xffU);
   ASSERT_FALSE(Idmap::FromBinaryStream(stream));
 }
 
 TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
   const size_t offset = kIdmapRawDataOffset;
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  kIdmapRawDataLen - offset);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
@@ -108,8 +109,7 @@
 
 TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
   const size_t offset = kIdmapRawDataOffset;
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  kIdmapRawDataLen - offset);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
@@ -134,7 +134,7 @@
 }
 
 TEST(IdmapTests, CreateIdmapFromBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream stream(raw);
 
   auto result = Idmap::FromBinaryStream(stream);
@@ -143,7 +143,7 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
   ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
@@ -177,7 +177,7 @@
 }
 
 TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data),
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData),
                   10);  // data too small
   std::istringstream stream(raw);
 
@@ -189,14 +189,14 @@
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  auto idmap_result = Idmap::FromApkAssets(
-      *target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
+  auto idmap_result = Idmap::FromContainers(
+      **target, **overlay, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
       /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
@@ -204,7 +204,7 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
@@ -218,15 +218,15 @@
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
-                                           TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
-                                           /* enforce_overlayable */ true);
+  auto idmap_result = Idmap::FromContainers(
+      **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
+      /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
@@ -255,25 +255,66 @@
   ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4);
 }
 
+TEST(IdmapTests, FabricatedOverlay) {
+  std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
+
+  auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
+                  .SetOverlayable("TestResources")
+                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
+                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+                  .Build();
+
+  ASSERT_TRUE(frro);
+  TemporaryFile tf;
+  std::ofstream out(tf.path);
+  ASSERT_TRUE((*frro).ToBinaryStream(out));
+  out.close();
+
+  auto overlay = OverlayResourceContainer::FromPath(tf.path);
+  ASSERT_TRUE(overlay);
+
+  auto idmap_result = Idmap::FromContainers(**target, **overlay, "SandTheme", PolicyFlags::PUBLIC,
+                                            /* enforce_overlayable */ true);
+  ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+  auto& idmap = *idmap_result;
+  ASSERT_THAT(idmap, NotNull());
+
+  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+  ASSERT_EQ(dataBlocks.size(), 1U);
+
+  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  ASSERT_THAT(data, NotNull());
+  ASSERT_EQ(data->GetTargetEntries().size(), 0U);
+  ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
+
+  const auto& target_inline_entries = data->GetTargetInlineEntries();
+  ASSERT_EQ(target_inline_entries.size(), 2U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
+                             Res_value::TYPE_INT_DEC, 2U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
+                             Res_value::TYPE_REFERENCE, 0x7f010000);
+}
+
 TEST(IdmapTests, FailCreateIdmapInvalidName) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
   {
-    auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", PolicyFlags::PUBLIC,
-                                             /* enforce_overlayable */ true);
+    auto idmap_result = Idmap::FromContainers(**target, **overlay, "", PolicyFlags::PUBLIC,
+                                              /* enforce_overlayable */ true);
     ASSERT_FALSE(idmap_result);
   }
   {
-    auto idmap_result =
-        Idmap::FromApkAssets(*target_apk, *overlay_apk, "unknown", PolicyFlags::PUBLIC,
-                             /* enforce_overlayable */ true);
+    auto idmap_result = Idmap::FromContainers(**target, **overlay, "unknown", PolicyFlags::PUBLIC,
+                                              /* enforce_overlayable */ true);
     ASSERT_FALSE(idmap_result);
   }
 }
@@ -282,15 +323,15 @@
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
-                                           TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
-                                           /* enforce_overlayable */ true);
+  auto idmap_result = Idmap::FromContainers(
+      **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
+      /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
@@ -328,30 +369,29 @@
 }
 
 Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
-    const std::string& local_target_apk_path, const std::string& local_overlay_apk_path,
+    const std::string& local_target_path, const std::string& local_overlay_path,
     const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
     bool enforce_overlayable) {
-  auto overlay_info =
-      utils::ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+  const std::string target_path(GetTestDataPath() + local_target_path);
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return Error(R"(Failed to load target "%s")", target_path.c_str());
+  }
+
+  const std::string overlay_path(GetTestDataPath() + local_overlay_path);
+  auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return Error(R"(Failed to load overlay "%s")", overlay_path.c_str());
+  }
+
+  auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
   if (!overlay_info) {
-    return overlay_info.GetError();
-  }
-
-  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
-  }
-
-  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+    return Error(R"(Failed to find overlay name "%s")", overlay_name.c_str());
   }
 
   LogInfo log_info;
-  auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
-                                                fulfilled_policies, enforce_overlayable, log_info);
+  auto mapping = ResourceMapping::FromContainers(**target, **overlay, *overlay_info,
+                                                 fulfilled_policies, enforce_overlayable, log_info);
   if (!mapping) {
     return mapping.GetError();
   }
@@ -360,11 +400,9 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
-  auto idmap_data =
-      TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", "DifferentPackages",
-
-                                 PolicyFlags::PUBLIC,
-                                 /* enforce_overlayable */ false);
+  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk",
+                                               "DifferentPackages", PolicyFlags::PUBLIC,
+                                               /* enforce_overlayable */ false);
 
   ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
   auto& data = *idmap_data;
@@ -417,7 +455,7 @@
   const uint32_t target_crc = kIdmapRawDataTargetCrc;
   const uint32_t overlay_crc = kIdmapRawOverlayCrc;
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   auto result = Idmap::FromBinaryStream(raw_stream);
@@ -468,8 +506,8 @@
   ASSERT_THAT(bad_target_crc_header, NotNull());
   ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
   ASSERT_FALSE(bad_target_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
-                                            target_crc, overlay_crc, policies,
-                                            /* enforce_overlayable */ true));
+                                                 target_crc, overlay_crc, policies,
+                                                 /* enforce_overlayable */ true));
 
   // overlay crc: bytes (0xc, 0xf)
   std::string bad_overlay_crc_string(stream.str());
@@ -483,8 +521,8 @@
   ASSERT_THAT(bad_overlay_crc_header, NotNull());
   ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
   ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
-                                            target_crc, overlay_crc, policies,
-                                            /* enforce_overlayable */ true));
+                                                  target_crc, overlay_crc, policies,
+                                                  /* enforce_overlayable */ true));
 
   // fulfilled policy: bytes (0x10, 0x13)
   std::string bad_policy_string(stream.str());
@@ -522,8 +560,8 @@
   ASSERT_THAT(bad_target_path_header, NotNull());
   ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
   ASSERT_FALSE(bad_target_path_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
-                                            target_crc, overlay_crc, policies,
-                                            /* enforce_overlayable */ true));
+                                                  target_crc, overlay_crc, policies,
+                                                  /* enforce_overlayable */ true));
 
   // overlay path: bytes (0x2c, 0x37)
   std::string bad_overlay_path_string(stream.str());
@@ -576,7 +614,7 @@
 };
 
 TEST(IdmapTests, TestVisitor) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(stream);
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
index 87ce0f1..3d3d82a 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -27,35 +27,31 @@
 #include "idmap2/Idmap.h"
 #include "idmap2/PrettyPrintVisitor.h"
 
-using android::ApkAssets;
 using android::base::StringPrintf;
-using ::testing::NotNull;
 
-using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
 TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
   const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
   const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk,
-                                          TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
-                                          /* enforce_overlayable */ true);
+  const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
+                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
   PrettyPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
-  ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+  ASSERT_NE(stream.str().find("target path  : "), std::string::npos);
+  ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
   ASSERT_NE(stream.str().find(StringPrintf("0x%08x -> 0x%08x (integer/int1 -> integer/int1)\n",
                                            R::target::integer::int1, R::overlay::integer::int1)),
             std::string::npos);
@@ -64,7 +60,7 @@
 TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -74,8 +70,8 @@
   PrettyPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
-  ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+  ASSERT_NE(stream.str().find("target path  : "), std::string::npos);
+  ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
   ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000 (\?\?\? -> \?\?\?)\n"), std::string::npos);
 }
 
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 88f85ef..a6371cb 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -30,17 +30,16 @@
 #include "idmap2/RawPrintVisitor.h"
 
 using android::base::StringPrintf;
-using ::testing::NotNull;
 
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
-#define ASSERT_CONTAINS_REGEX(pattern, str)                       \
-  do {                                                            \
-    ASSERT_TRUE(std::regex_search(str, std::regex(pattern)))      \
-        << "pattern '" << pattern << "' not found in\n--------\n" \
-        << str << "--------";                                     \
+#define ASSERT_CONTAINS_REGEX(pattern, str)                         \
+  do {                                                              \
+    ASSERT_TRUE(std::regex_search(str, std::regex(pattern)))        \
+        << "pattern '" << (pattern) << "' not found in\n--------\n" \
+        << (str) << "--------";                                     \
   } while (0)
 
 #define ADDRESS "[0-9a-f]{8}: "
@@ -49,16 +48,15 @@
   fclose(stderr);  // silence expected warnings
 
   const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
   const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  const auto idmap =
-      Idmap::FromApkAssets(*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_DEFAULT,
-                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
+  const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
+                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
@@ -66,7 +64,7 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000007  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000008  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(
       StringPrintf(ADDRESS "%s  target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
       stream.str());
@@ -75,8 +73,6 @@
       stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  fulfilled policies: public\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  enforce overlayable\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  target entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000000  target inline entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count", stream.str());
@@ -104,7 +100,7 @@
 TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -115,7 +111,7 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000007  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000008  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00001234  target crc\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00005678  overlay crc\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000011  fulfilled policies: public|signature\n", stream.str());
@@ -126,8 +122,6 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "........  overlay path: overlayX.apk\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "0000000b  overlay name size\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "........  overlay name: OverlayName\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000003  target entry count\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  target inline entry count\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000003  overlay entry count\n", stream.str());
@@ -140,7 +134,7 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  overlay id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f030002  target id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  string pool size\n", stream.str());
-  ASSERT_CONTAINS_REGEX("000000a8: ........  string pool\n", stream.str());
+  ASSERT_CONTAINS_REGEX("000000a4: ........  string pool\n", stream.str());
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 0362529..5a1d808 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#include <android-base/file.h>
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
 #include <cstdio>  // fclose
 #include <fstream>
 #include <memory>
@@ -22,14 +26,10 @@
 #include "R.h"
 #include "TestConstants.h"
 #include "TestHelpers.h"
-#include "androidfw/ResourceTypes.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
 #include "idmap2/LogInfo.h"
 #include "idmap2/ResourceMapping.h"
 
 using android::Res_value;
-using android::idmap2::utils::ExtractOverlayManifestInfo;
 
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
@@ -41,32 +41,36 @@
     ASSERT_TRUE(result) << result.GetErrorMessage(); \
   } while (0)
 
-Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_apk_path,
-                                               const std::string& local_overlay_apk_path,
+Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_path,
+                                               const std::string& local_overlay_path,
                                                const std::string& overlay_name,
                                                const PolicyBitmask& fulfilled_policies,
                                                bool enforce_overlayable) {
-  auto overlay_info =
-      ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+  const std::string target_path = (local_target_path[0] == '/')
+                                      ? local_target_path
+                                      : (GetTestDataPath() + "/" + local_target_path);
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return Error(target.GetError(), R"(Failed to load target "%s")", target_path.c_str());
+  }
+
+  const std::string overlay_path = (local_overlay_path[0] == '/')
+                                       ? local_overlay_path
+                                       : (GetTestDataPath() + "/" + local_overlay_path);
+  auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return Error(overlay.GetError(), R"(Failed to load overlay "%s")", overlay_path.c_str());
+  }
+
+  auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
   if (!overlay_info) {
-    return overlay_info.GetError();
-  }
-
-  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
-  }
-
-  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+    return Error(overlay_info.GetError(), R"(Failed to find overlay name "%s")",
+                 overlay_name.c_str());
   }
 
   LogInfo log_info;
-  return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
-                                        fulfilled_policies, enforce_overlayable, log_info);
+  return ResourceMapping::FromContainers(**target, **overlay, *overlay_info, fulfilled_policies,
+                                         enforce_overlayable, log_info);
 }
 
 Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_resource,
@@ -128,7 +132,7 @@
 }
 
 TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
@@ -145,7 +149,7 @@
 }
 
 TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "SwapNames",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "SwapNames",
                                           PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -161,7 +165,7 @@
 }
 
 TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           "DifferentPackages", PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -176,7 +180,7 @@
 }
 
 TEST(ResourceMappingTests, InlineResources) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "Inline",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "Inline",
                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
 
   constexpr size_t overlay_string_pool_size = 10U;
@@ -189,8 +193,32 @@
   ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 73U));
 }
 
+TEST(ResourceMappingTests, FabricatedOverlay) {
+  auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
+                  .SetOverlayable("TestResources")
+                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
+                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+                  .Build();
+
+  ASSERT_TRUE(frro);
+  TemporaryFile tf;
+  std::ofstream out(tf.path);
+  ASSERT_TRUE((*frro).ToBinaryStream(out));
+  out.close();
+
+  auto resources = TestGetResourceMapping("target/target.apk", tf.path, "SandTheme",
+                                          PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+  ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
+  ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
+}
+
 TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ true);
@@ -209,7 +237,7 @@
 // Resources that are not declared as overlayable and resources that a protected by policies the
 // overlay does not fulfill must not map to overlay resources.
 TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ true);
@@ -229,7 +257,7 @@
 // overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
 // off.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
@@ -264,7 +292,7 @@
 // Overlays that do not target an <overlayable> tag can overlay any resource if overlayable
 // enforcement is disabled.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
                                           PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -284,10 +312,9 @@
 // Overlays that are neither pre-installed nor signed with the same signature as the target cannot
 // overlay packages that have not defined overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
-  auto resources =
-      TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
-                             "NoTargetName", PolicyFlags::PUBLIC,
-                             /* enforce_overlayable */ true);
+  auto resources = TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
+                                          "NoTargetName", PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
@@ -297,9 +324,9 @@
 // signed with the same signature as the reference package can overlay packages that have not
 // defined overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
-  auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
+  auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) {
     auto resources =
-        TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
+        TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
                                TestConstants::OVERLAY_NAME_ALL_POLICIES, fulfilled_policies,
                                /* enforce_overlayable */ true);
 
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
index 1f6bf49..6914208 100644
--- a/cmds/idmap2/tests/ResourceUtilsTests.cpp
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -17,10 +17,12 @@
 #include <memory>
 #include <string>
 
+#include "R.h"
 #include "TestHelpers.h"
 #include "androidfw/ApkAssets.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "idmap2/ResourceContainer.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 
@@ -49,8 +51,8 @@
 };
 
 TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
-  Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000U);
-  ASSERT_TRUE(name);
+  Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), R::target::integer::int1);
+  ASSERT_TRUE(name) << name.GetErrorMessage();
   ASSERT_EQ(*name, "integer/int1");
 }
 
@@ -60,25 +62,34 @@
 }
 
 TEST_F(ResourceUtilsTests, InvalidValidOverlayNameInvalidAttributes) {
-  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
-                                                "InvalidName");
+  auto overlay =
+      OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+  ASSERT_TRUE(overlay);
+
+  auto info = (*overlay)->FindOverlayInfo("InvalidName");
   ASSERT_FALSE(info);
 }
 
 TEST_F(ResourceUtilsTests, ValidOverlayNameInvalidAttributes) {
-  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
-                                                "ValidName");
+  auto overlay =
+      OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+  ASSERT_TRUE(overlay);
+
+  auto info = (*overlay)->FindOverlayInfo("ValidName");
   ASSERT_FALSE(info);
 }
 
 TEST_F(ResourceUtilsTests, ValidOverlayNameAndTargetPackageInvalidAttributes) {
-  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
-                                                "ValidNameAndTargetPackage");
+  auto overlay =
+      OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+  ASSERT_TRUE(overlay);
+
+  auto info = (*overlay)->FindOverlayInfo("ValidNameAndTargetPackage");
   ASSERT_TRUE(info);
   ASSERT_EQ("ValidNameAndTargetPackage", info->name);
   ASSERT_EQ("Valid", info->target_package);
-  ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
-  ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
+  ASSERT_EQ("", info->target_name);      // Attribute resource id could not be found
+  ASSERT_EQ(0, info->resource_mapping);  // Attribute resource id could not be found
 }
 
-}// namespace android::idmap2
+}  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index 842af3d..6b5f3a8 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -24,13 +24,13 @@
 
 namespace android::idmap2 {
 
-const unsigned char idmap_raw_data[] = {
+const unsigned char kIdmapRawData[] = {
     // IDMAP HEADER
     // 0x0: magic
     0x49, 0x44, 0x4d, 0x50,
 
     // 0x4: version
-    0x07, 0x00, 0x00, 0x00,
+    0x08, 0x00, 0x00, 0x00,
 
     // 0x8: target crc
     0x34, 0x12, 0x00, 0x00,
@@ -70,81 +70,72 @@
     0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
 
     // DATA HEADER
-    // 0x54: target_package_id
-    0x7f,
-
-    // 0x55: overlay_package_id
-    0x7f,
-
-    // 0x56: padding
-    0x00, 0x00,
-
-    // 0x58: target_entry_count
+    // 0x54: target_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x5c: target_inline_entry_count
+    // 0x58: target_inline_entry_count
     0x01, 0x00, 0x00, 0x00,
 
-    // 0x60: overlay_entry_count
+    // 0x5c: overlay_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x64: string_pool_offset
+    // 0x60: string_pool_offset
     0x00, 0x00, 0x00, 0x00,
 
     // TARGET ENTRIES
-    // 0x68: target id (0x7f020000)
+    // 0x64: target id (0x7f020000)
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x6c: overlay_id (0x7f020000)
+    // 0x68: overlay_id (0x7f020000)
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x70: target id (0x7f030000)
+    // 0x6c: target id (0x7f030000)
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x74: overlay_id (0x7f030000)
+    // 0x70: overlay_id (0x7f030000)
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x78: target id (0x7f030002)
+    // 0x74: target id (0x7f030002)
     0x02, 0x00, 0x03, 0x7f,
 
-    // 0x7c: overlay_id (0x7f030001)
+    // 0x78: overlay_id (0x7f030001)
     0x01, 0x00, 0x03, 0x7f,
 
     // INLINE TARGET ENTRIES
 
-    // 0x80: target_id
+    // 0x7c: target_id
     0x00, 0x00, 0x04, 0x7f,
 
-    // 0x84: Res_value::size (value ignored by idmap)
+    // 0x80: Res_value::size (value ignored by idmap)
     0x08, 0x00,
 
-    // 0x87: Res_value::res0 (value ignored by idmap)
+    // 0x82: Res_value::res0 (value ignored by idmap)
     0x00,
 
-    // 0x88: Res_value::dataType (TYPE_INT_HEX)
+    // 0x83: Res_value::dataType (TYPE_INT_HEX)
     0x11,
 
-    // 0x8c: Res_value::data
+    // 0x84: Res_value::data
     0x78, 0x56, 0x34, 0x12,
 
     // OVERLAY ENTRIES
-    // 0x90: 0x7f020000 -> 0x7f020000
+    // 0x88: 0x7f020000 -> 0x7f020000
     0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
 
-    // 0x98: 0x7f030000 -> 0x7f030000
+    // 0x90: 0x7f030000 -> 0x7f030000
     0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
 
-    // 0xa0: 0x7f030001 -> 0x7f030002
+    // 0x98: 0x7f030001 -> 0x7f030002
     0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
 
-    // 0xa4: string pool
+    // 0xa0: string pool
     // string length,
     0x04, 0x00, 0x00, 0x00,
 
-    // 0xa8 string contents "test"
+    // 0xa4 string contents "test"
     0x74, 0x65, 0x73, 0x74};
 
-const unsigned int kIdmapRawDataLen = 0xac;
+const unsigned int kIdmapRawDataLen = 0xa8;
 const unsigned int kIdmapRawDataOffset = 0x54;
 const unsigned int kIdmapRawDataTargetCrc = 0x1234;
 const unsigned int kIdmapRawOverlayCrc = 0x5678;
diff --git a/cmds/idmap2/tests/XmlParserTests.cpp b/cmds/idmap2/tests/XmlParserTests.cpp
index 1a7eaca..eaf10a7 100644
--- a/cmds/idmap2/tests/XmlParserTests.cpp
+++ b/cmds/idmap2/tests/XmlParserTests.cpp
@@ -19,25 +19,25 @@
 #include <string>
 
 #include "TestHelpers.h"
-#include "gmock/gmock.h"
+#include "androidfw/AssetsProvider.h"
 #include "gtest/gtest.h"
 #include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
-Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+Result<XmlParser> CreateTestParser(const std::string& test_file) {
+  auto zip = ZipAssetsProvider::Create(GetTestDataPath() + "/target/target.apk");
   if (zip == nullptr) {
     return Error("Failed to open zip file");
   }
 
-  auto data = zip->Uncompress(test_file);
+  auto data = zip->Open(test_file);
   if (data == nullptr) {
     return Error("Failed to open xml file");
   }
 
-  return XmlParser::Create(data->buf, data->size, /* copy_data */ true);
+  return XmlParser::Create(data->getBuffer(true /* aligned*/), data->getLength(),
+                           /* copy_data */ true);
 }
 
 TEST(XmlParserTests, Create) {
@@ -54,7 +54,7 @@
   auto xml = CreateTestParser("res/xml/test.xml");
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
-  auto root_iter = (*xml)->tree_iterator();
+  auto root_iter = xml->tree_iterator();
   ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG);
   ASSERT_EQ(root_iter->name(), "a");
 
@@ -85,7 +85,7 @@
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
   // Start at the <a> tag.
-  auto root_iter = (*xml)->tree_iterator();
+  auto root_iter = xml->tree_iterator();
 
   // Start at the <b> tag.
   auto a_iter = root_iter.begin();
@@ -111,8 +111,8 @@
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
   // Start at the <a> tag.
-  auto root_iter_1 = (*xml)->tree_iterator();
-  auto root_iter_2 = (*xml)->tree_iterator();
+  auto root_iter_1 = xml->tree_iterator();
+  auto root_iter_2 = xml->tree_iterator();
   ASSERT_EQ(root_iter_1, root_iter_2);
   ASSERT_EQ(*root_iter_1, *root_iter_2);
 
@@ -146,7 +146,7 @@
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
   // Start at the <a> tag.
-  auto root_iter_1 = (*xml)->tree_iterator();
+  auto root_iter_1 = xml->tree_iterator();
 
   // Start at the <b> tag.
   auto a_iter_1 = root_iter_1.begin();
diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp
deleted file mode 100644
index 3fca436..0000000
--- a/cmds/idmap2/tests/ZipFileTests.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdio>  // fclose
-#include <string>
-
-#include "TestHelpers.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "idmap2/Result.h"
-#include "idmap2/ZipFile.h"
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-
-namespace android::idmap2 {
-
-TEST(ZipFileTests, BasicOpen) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  fclose(stderr);  // silence expected warnings from libziparchive
-  auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist");
-  ASSERT_THAT(fail, IsNull());
-}
-
-TEST(ZipFileTests, Crc) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  Result<uint32_t> crc = zip->Crc("AndroidManifest.xml");
-  ASSERT_TRUE(crc);
-  ASSERT_EQ(*crc, 0x762f3d24);
-
-  Result<uint32_t> crc2 = zip->Crc("does-not-exist");
-  ASSERT_FALSE(crc2);
-}
-
-TEST(ZipFileTests, Uncompress) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  auto data = zip->Uncompress("assets/lorem-ipsum.txt");
-  ASSERT_THAT(data, NotNull());
-  const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n");
-  ASSERT_THAT(data->size, lorem_ipsum.size());
-  ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum);
-
-  auto fail = zip->Uncompress("does-not-exist");
-  ASSERT_THAT(fail, IsNull());
-}
-
-}  // namespace android::idmap2
diff --git a/core/api/current.txt b/core/api/current.txt
index 19c8710..c72608f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -435,6 +435,7 @@
     field public static final int clickable = 16842981; // 0x10100e5
     field public static final int clipChildren = 16842986; // 0x10100ea
     field public static final int clipOrientation = 16843274; // 0x101020a
+    field public static final int clipToOutline = 16844328; // 0x1010628
     field public static final int clipToPadding = 16842987; // 0x10100eb
     field public static final int closeIcon = 16843905; // 0x1010481
     field @Deprecated public static final int codes = 16843330; // 0x1010242
@@ -572,6 +573,7 @@
     field public static final int dropDownWidth = 16843362; // 0x1010262
     field public static final int duplicateParentState = 16842985; // 0x10100e9
     field public static final int duration = 16843160; // 0x1010198
+    field public static final int edgeEffectType = 16844329; // 0x1010629
     field public static final int editTextBackground = 16843602; // 0x1010352
     field public static final int editTextColor = 16843601; // 0x1010351
     field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -847,6 +849,7 @@
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
     field public static final int killAfterRestore = 16843420; // 0x101029c
+    field public static final int knownCerts = 16844330; // 0x101062a
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
@@ -2936,12 +2939,12 @@
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
-    field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
     field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
     field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
     field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
     field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
     field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
+    field public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43; // 0x2b
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
@@ -5554,15 +5557,19 @@
     field public static final int DEFAULT_LIGHTS = 4; // 0x4
     field public static final int DEFAULT_SOUND = 1; // 0x1
     field public static final int DEFAULT_VIBRATE = 2; // 0x2
+    field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
     field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
     field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
     field public static final String EXTRA_BIG_TEXT = "android.bigText";
+    field public static final String EXTRA_CALL_PERSON = "android.callPerson";
     field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
     field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
     field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
     field public static final String EXTRA_COLORIZED = "android.colorized";
     field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
     field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+    field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+    field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
     field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final String EXTRA_INFO_TEXT = "android.infoText";
     field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
@@ -5594,6 +5601,8 @@
     field public static final String EXTRA_TEXT_LINES = "android.textLines";
     field public static final String EXTRA_TITLE = "android.title";
     field public static final String EXTRA_TITLE_BIG = "android.title.big";
+    field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+    field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
     field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
     field public static final int FLAG_BUBBLE = 4096; // 0x1000
     field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
@@ -5842,6 +5851,14 @@
     method @NonNull public android.app.Notification.Builder setWhen(long);
   }
 
+  public static class Notification.CallStyle extends android.app.Notification.Style {
+    method @NonNull public static android.app.Notification.CallStyle forIncomingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+    method @NonNull public static android.app.Notification.CallStyle forOngoingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent);
+    method @NonNull public static android.app.Notification.CallStyle forScreeningCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+    method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon);
+    method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence);
+  }
+
   public static final class Notification.CarExtender implements android.app.Notification.Extender {
     ctor public Notification.CarExtender();
     ctor public Notification.CarExtender(android.app.Notification);
@@ -10964,6 +10981,7 @@
     field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
     field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
     field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+    field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
     field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
@@ -23017,10 +23035,12 @@
     method @Deprecated public int getStreamType();
     method public String getTitle(android.content.Context);
     method public float getVolume();
+    method public boolean isHapticGeneratorEnabled();
     method public boolean isLooping();
     method public boolean isPlaying();
     method public void play();
     method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
+    method public boolean setHapticGeneratorEnabled(boolean);
     method public void setLooping(boolean);
     method @Deprecated public void setStreamType(int);
     method public void setVolume(float);
@@ -30594,6 +30614,7 @@
     field public static String DIRECTORY_NOTIFICATIONS;
     field public static String DIRECTORY_PICTURES;
     field public static String DIRECTORY_PODCASTS;
+    field @NonNull public static String DIRECTORY_RECORDINGS;
     field public static String DIRECTORY_RINGTONES;
     field public static String DIRECTORY_SCREENSHOTS;
     field public static final String MEDIA_BAD_REMOVAL = "bad_removal";
@@ -40338,6 +40359,7 @@
     field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
+    field public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL = "store_sim_pin_for_unattended_reboot_bool";
     field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
     field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
     field public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL = "support_add_conference_participants_bool";
@@ -42064,7 +42086,7 @@
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState();
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -42923,6 +42945,16 @@
     field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2
   }
 
+  public final class ImsRegistrationAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAttributeFlags();
+    method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
+    method public int getTransportType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR;
+  }
+
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException;
   }
@@ -42932,7 +42964,6 @@
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
-    field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
     field public static final int REGISTRATION_STATE_NOT_REGISTERED = 0; // 0x0
     field public static final int REGISTRATION_STATE_REGISTERED = 2; // 0x2
     field public static final int REGISTRATION_STATE_REGISTERING = 1; // 0x1
@@ -42941,9 +42972,9 @@
   public static class RegistrationManager.RegistrationCallback {
     ctor public RegistrationManager.RegistrationCallback();
     method @Deprecated public void onRegistered(int);
-    method public void onRegistered(int, int);
+    method public void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method @Deprecated public void onRegistering(int);
-    method public void onRegistering(int, int);
+    method public void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public void onTechnologyChangeFailed(int, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo);
   }
@@ -50688,6 +50719,7 @@
     method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
     field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
+    field public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET = "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
     field public static final String EXTRA_CLIENT_STATE = "android.view.autofill.extra.CLIENT_STATE";
   }
 
@@ -53560,6 +53592,7 @@
     method @Nullable public android.graphics.BlendMode getBlendMode();
     method @ColorInt public int getColor();
     method public int getMaxHeight();
+    method public int getType();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
@@ -53568,7 +53601,10 @@
     method public void setBlendMode(@Nullable android.graphics.BlendMode);
     method public void setColor(@ColorInt int);
     method public void setSize(int, int);
+    method public void setType(int);
     field public static final android.graphics.BlendMode DEFAULT_BLEND_MODE;
+    field public static final int TYPE_GLOW = 0; // 0x0
+    field public static final int TYPE_STRETCH = 1; // 0x1
   }
 
   public class EditText extends android.widget.TextView {
@@ -54635,6 +54671,8 @@
     method public void setTextViewText(@IdRes int, CharSequence);
     method public void setTextViewTextSize(@IdRes int, int, float);
     method public void setUri(@IdRes int, String, android.net.Uri);
+    method public void setViewOutlinePreferredRadius(@IdRes int, float, int);
+    method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int);
     method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
     method public void setViewVisibility(@IdRes int, int);
     method public void showNext(@IdRes int);
@@ -54659,6 +54697,12 @@
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface RemoteViews.RemoteView {
   }
 
+  public static final class RemoteViews.RemoteViewOutlineProvider extends android.view.ViewOutlineProvider {
+    ctor public RemoteViews.RemoteViewOutlineProvider(float);
+    method public void getOutline(@NonNull android.view.View, @NonNull android.graphics.Outline);
+    method public float getRadius();
+  }
+
   public abstract class RemoteViewsService extends android.app.Service {
     ctor public RemoteViewsService();
     method public android.os.IBinder onBind(android.content.Intent);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index bf70803..4e25625 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -162,6 +162,7 @@
   }
 
   public class ConnectivityManager {
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5bb3e05..fdd1e66 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -179,6 +179,7 @@
     field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
     field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
     field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
+    field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
@@ -338,9 +339,8 @@
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
-    field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+    field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
-    field public static final int config_systemVideoCall = 17039401; // 0x1040029
   }
 
   public static final class R.style {
@@ -2632,6 +2632,7 @@
     field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
     field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
+    field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
     field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
@@ -7044,11 +7045,15 @@
     method public long getExpiryTimeMillis();
     method public long getRefreshTimeMillis();
     method @Nullable public android.net.Uri getUserPortalUrl();
+    method public int getUserPortalUrlSource();
     method @Nullable public String getVenueFriendlyName();
     method @Nullable public android.net.Uri getVenueInfoUrl();
+    method public int getVenueInfoUrlSource();
     method public boolean isCaptive();
     method public boolean isSessionExtendable();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
   }
 
@@ -7062,8 +7067,10 @@
     method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
     method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
     method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
     method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String);
     method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
   }
 
   public class ConnectivityManager {
@@ -11869,6 +11876,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+    method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
@@ -11993,6 +12001,9 @@
     field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
     field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
     field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
+    field public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2; // 0x2
+    field public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1; // 0x1
+    field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0
     field public static final int RADIO_POWER_OFF = 0; // 0x0
     field public static final int RADIO_POWER_ON = 1; // 0x1
     field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
@@ -12974,6 +12985,16 @@
     field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service";
   }
 
+  public final class ImsRegistrationAttributes implements android.os.Parcelable {
+    method public int getRegistrationTechnology();
+  }
+
+  public static final class ImsRegistrationAttributes.Builder {
+    ctor public ImsRegistrationAttributes.Builder(int);
+    method @NonNull public android.telephony.ims.ImsRegistrationAttributes build();
+    method @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFeatureTags(@NonNull java.util.Set<java.lang.String>);
+  }
+
   public class ImsService extends android.app.Service {
     ctor public ImsService();
     method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int);
@@ -13757,7 +13778,9 @@
     ctor public ImsRegistrationImplBase();
     method public final void onDeregistered(android.telephony.ims.ImsReasonInfo);
     method public final void onRegistered(int);
+    method public final void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onRegistering(int);
+    method public final void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]);
     method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo);
     method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e39b2b8..0611031 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -14,6 +14,7 @@
     field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
     field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
+    field public static final String KEEP_UNINSTALLED_PACKAGES = "android.permission.KEEP_UNINSTALLED_PACKAGES";
     field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
@@ -53,9 +54,8 @@
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
-    field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+    field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
-    field public static final int config_systemVideoCall = 17039401; // 0x1040029
   }
 
 }
@@ -472,6 +472,7 @@
     method @NonNull public String getOwnerName();
     method @Nullable public String getTimeZone();
     method public boolean isLeaveAllSystemAppsEnabled();
+    method public void logParams(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
   }
@@ -495,6 +496,7 @@
     method public boolean isKeepAccountMigrated();
     method public boolean isLeaveAllSystemAppsEnabled();
     method public boolean isOrganizationOwnedProvisioning();
+    method public void logParams(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 4b2d741..768ec38 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -20,12 +20,12 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
@@ -97,10 +97,10 @@
           GESTURE_UNKNOWN,
           GESTURE_TOUCH_EXPLORATION,
             GESTURE_2_FINGER_SINGLE_TAP,
-            GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_DOUBLE_TAP,
             GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_TRIPLE_TAP,
+            GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_SINGLE_TAP,
             GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_DOUBLE_TAP,
@@ -232,8 +232,8 @@
             case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH";
             case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION";
             case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
-            case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD:
-                return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD";
+            case GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD:
+                return "GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD";
             case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
             case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
                 return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 1fa7fa2..dab4a5d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -445,8 +445,8 @@
     /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
     public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
 
-    /** The user has performed a two-finger  single-tap and hold gesture on the touch screen. */
-    public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43;
+    /** The user has performed a two-finger  triple-tap and hold gesture on the touch screen. */
+    public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43;
 
     /** The user has performed a three-finger  single-tap and hold gesture on the touch screen. */
     public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 43d0269..e2426d1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -50,7 +50,9 @@
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Icon;
+import android.hardware.HardwareBuffer;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
@@ -76,6 +78,7 @@
 import android.util.Size;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
+import android.window.TaskSnapshot;
 
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.procstats.ProcessStats;
@@ -1740,6 +1743,62 @@
      */
     public static class RecentTaskInfo extends TaskInfo implements Parcelable {
         /**
+         * @hide
+         */
+        public static class PersistedTaskSnapshotData {
+            /**
+             * The bounds of the task when the last snapshot was taken, may be null if the task is
+             * not yet attached to the hierarchy.
+             * @see {@link android.window.TaskSnapshot#mTaskSize}.
+             * @hide
+             */
+            public @Nullable Point taskSize;
+
+            /**
+             * The content insets of the task when the task snapshot was taken.
+             * @see {@link android.window.TaskSnapshot#mContentInsets}.
+             * @hide
+             */
+            public @Nullable Rect contentInsets;
+
+            /**
+             * The size of the last snapshot taken, may be null if there is no associated snapshot.
+             * @see {@link android.window.TaskSnapshot#mSnapshot}.
+             * @hide
+             */
+            public @Nullable Point bufferSize;
+
+            /**
+             * Sets the data from the other data.
+             * @hide
+             */
+            public void set(PersistedTaskSnapshotData other) {
+                taskSize = other.taskSize;
+                contentInsets = other.contentInsets;
+                bufferSize = other.bufferSize;
+            }
+
+            /**
+             * Sets the data from the provided {@param snapshot}.
+             * @hide
+             */
+            public void set(TaskSnapshot snapshot) {
+                if (snapshot == null) {
+                    taskSize = null;
+                    contentInsets = null;
+                    bufferSize = null;
+                    return;
+                }
+                final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+                taskSize = new Point(snapshot.getTaskSize());
+                contentInsets = new Rect(snapshot.getContentInsets());
+                bufferSize = buffer != null
+                        ? new Point(buffer.getWidth(), buffer.getHeight())
+                        : null;
+            }
+        }
+
+        /**
          * If this task is currently running, this is the identifier for it.
          * If it is not running, this will be -1.
          *
@@ -1782,6 +1841,12 @@
          */
         public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
 
+        /**
+         * Information about the last snapshot taken for this task.
+         * @hide
+         */
+        public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
+
         public RecentTaskInfo() {
         }
 
@@ -1798,6 +1863,9 @@
             id = source.readInt();
             persistentId = source.readInt();
             childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader());
+            lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
+            lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
+            lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
             super.readFromParcel(source);
         }
 
@@ -1806,6 +1874,9 @@
             dest.writeInt(id);
             dest.writeInt(persistentId);
             dest.writeList(childrenTaskInfos);
+            dest.writeTypedObject(lastSnapshotData.taskSize, flags);
+            dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
+            dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
             super.writeToParcel(dest, flags);
         }
 
@@ -1825,7 +1896,6 @@
         public void dump(PrintWriter pw, String indent) {
             pw.println(); pw.print("   ");
             pw.print(" id="); pw.print(persistentId);
-            pw.print(" stackId="); pw.print(stackId);
             pw.print(" userId="); pw.print(userId);
             pw.print(" hasTask="); pw.print((id != -1));
             pw.print(" lastActiveTime="); pw.println(lastActiveTime);
@@ -1872,6 +1942,12 @@
                 pw.print(Integer.toHexString(td.getBackgroundColorFloating()));
                 pw.println(" }");
             }
+            pw.print("   ");
+            pw.print(" lastSnapshotData {");
+            pw.print(" taskSize=" + lastSnapshotData.taskSize);
+            pw.print(" contentInsets=" + lastSnapshotData.contentInsets);
+            pw.print(" bufferSize=" + lastSnapshotData.bufferSize);
+            pw.println(" }");
         }
     }
 
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 8b6570f..ac1fa1e 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -16,7 +16,9 @@
 
 package android.app;
 
+import android.Manifest;
 import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.UserHandleAware;
 import android.content.Context;
@@ -73,8 +75,8 @@
     /**
      * Returns the game mode for the given package.
      */
-    // TODO(b/178111358): Add @RequiresPermission.
     @UserHandleAware
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public @GameMode int getGameMode(String packageName) {
         try {
             return mService.getGameMode(packageName, mContext.getUserId());
@@ -86,8 +88,8 @@
     /**
      * Sets the game mode for the given package.
      */
-    // TODO(b/178111358): Add @RequiresPermission.
     @UserHandleAware
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public void setGameMode(String packageName, @GameMode int gameMode) {
         try {
             mService.setGameMode(packageName, gameMode, mContext.getUserId());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49f508d..050f34a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,7 +22,10 @@
 
 import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.ColorInt;
+import android.annotation.ColorRes;
 import android.annotation.DimenRes;
 import android.annotation.Dimension;
 import android.annotation.DrawableRes;
@@ -33,6 +36,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringRes;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -403,6 +407,7 @@
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
+        STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
     }
 
@@ -649,7 +654,7 @@
     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
-            MessagingStyle.class);
+            MessagingStyle.class, CallStyle.class);
 
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
@@ -1318,6 +1323,53 @@
     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
 
     /**
+     * {@link #extras} key: the type of call represented by the
+     * {@link android.app.Notification.CallStyle} notification. This extra is an int.
+     * @hide
+     */
+    public static final String EXTRA_CALL_TYPE = "android.callType";
+
+    /**
+     * {@link #extras} key: the person to be displayed as calling for the
+     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
+     */
+    public static final String EXTRA_CALL_PERSON = "android.callPerson";
+
+    /**
+     * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
+     * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
+     */
+    public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+
+    /**
+     * {@link #extras} key: the text to be displayed as a verification status of the caller on a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link CharSequence}.
+     */
+    public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
+
+    /**
+     * {@link #extras} key: the intent to be sent when the users answers a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link PendingIntent}.
+     */
+    public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
+
+    /**
+     * {@link #extras} key: the intent to be sent when the users declines a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link PendingIntent}.
+     */
+    public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+
+    /**
+     * {@link #extras} key: the intent to be sent when the users hangs up a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link PendingIntent}.
+     */
+    public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
+
+    /**
      * {@link #extras} key: whether the notification should be colorized as
      * supplied to {@link Builder#setColorized(boolean)}.
      */
@@ -5876,11 +5928,11 @@
             return summary;
         }
 
-        private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
+        private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
                 StandardTemplateParams p) {
             final boolean tombstone = (action.actionIntent == null);
             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
-                    emphazisedMode ? getEmphasizedActionLayoutResource()
+                    emphasizedMode ? getEmphasizedActionLayoutResource()
                             : tombstone ? getActionTombstoneLayoutResource()
                                     : getActionLayoutResource());
             if (!tombstone) {
@@ -5890,43 +5942,42 @@
             if (action.mRemoteInputs != null) {
                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
             }
-            if (emphazisedMode) {
+            if (emphasizedMode) {
                 // change the background bgColor
                 CharSequence title = action.title;
-                ColorStateList[] outResultColor = null;
+                ColorStateList[] outResultColor = new ColorStateList[1];
                 int background = resolveBackgroundColor(p);
                 if (isLegacy()) {
                     title = ContrastColorUtil.clearColorSpans(title);
                 } else {
-                    outResultColor = new ColorStateList[1];
                     title = ensureColorSpanContrast(title, background, outResultColor);
                 }
                 button.setTextViewText(R.id.action0, processTextSpans(title));
-                setTextViewColorPrimary(button, R.id.action0, p);
-                int rippleColor;
-                boolean hasColorOverride = outResultColor != null && outResultColor[0] != null;
+                int textColor = getPrimaryTextColor(p);
+                boolean hasColorOverride = outResultColor[0] != null;
                 if (hasColorOverride) {
                     // There's a span spanning the full text, let's take it and use it as the
                     // background color
                     background = outResultColor[0].getDefaultColor();
-                    int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+                    textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                             background, mInNightMode);
-                    button.setTextColor(R.id.action0, textColor);
-                    rippleColor = textColor;
                 } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
                         && mTintActionButtons && !mInNightMode) {
-                    rippleColor = resolveContrastColor(p);
-                    button.setTextColor(R.id.action0, rippleColor);
-                } else {
-                    rippleColor = getPrimaryTextColor(p);
+                    textColor = resolveContrastColor(p);
                 }
+                button.setTextColor(R.id.action0, textColor);
                 // We only want about 20% alpha for the ripple
-                rippleColor = (rippleColor & 0x00ffffff) | 0x33000000;
+                final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
                 button.setColorStateList(R.id.action0, "setRippleColor",
                         ColorStateList.valueOf(rippleColor));
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(background));
                 button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
+                if (p.mAllowActionIcons) {
+                    button.setImageViewIcon(R.id.action0, action.getIcon());
+                    boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
+                    button.setBoolean(R.id.action0, "setWrapModePriority", priority);
+                }
             } else {
                 button.setTextViewText(R.id.action0, processTextSpans(
                         processLegacyText(action.title)));
@@ -5936,8 +5987,12 @@
                     button.setTextColor(R.id.action0, resolveContrastColor(p));
                 }
             }
-            button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
-                    mActions.indexOf(action));
+            // CallStyle notifications add action buttons which don't actually exist in mActions,
+            //  so we have to omit the index in that case.
+            int actionIndex = mActions.indexOf(action);
+            if (actionIndex != -1) {
+                button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
+            }
             return button;
         }
 
@@ -6371,6 +6426,10 @@
             return R.layout.notification_template_material_conversation;
         }
 
+        private int getCallLayoutResource() {
+            return R.layout.notification_template_material_call;
+        }
+
         private int getActionLayoutResource() {
             return R.layout.notification_material_action;
         }
@@ -8039,6 +8098,10 @@
                             : mBuilder.getMessagingLayoutResource(),
                     p,
                     bindResult);
+            if (isConversationLayout) {
+                mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+                mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+            }
 
             addExtras(mBuilder.mN.extras);
             if (!isConversationLayout) {
@@ -8925,6 +8988,441 @@
         }
     }
 
+
+
+    /**
+     * Helper class for generating large-format notifications that include a large image attachment.
+     *
+     * Here's how you'd set the <code>CallStyle</code> on a notification:
+     * <pre class="prettyprint">
+     * Notification notif = new Notification.Builder(mContext)
+     *     .setSmallIcon(R.drawable.new_post)
+     *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
+     *     .build();
+     * </pre>
+     */
+    public static class CallStyle extends Style {
+        private static final int CALL_TYPE_INCOMING = 1;
+        private static final int CALL_TYPE_ONGOING = 2;
+        private static final int CALL_TYPE_SCREENING = 3;
+
+        /**
+         * This is a key used privately on the action.extras to give spacing priority
+         * to the required call actions
+         */
+        private static final String KEY_ACTION_PRIORITY = "key_action_priority";
+
+        private int mCallType;
+        private Person mPerson;
+        private PendingIntent mAnswerIntent;
+        private PendingIntent mDeclineIntent;
+        private PendingIntent mHangUpIntent;
+        private Icon mVerificationIcon;
+        private CharSequence mVerificationText;
+
+        CallStyle() {
+        }
+
+        /**
+         * Create a CallStyle for an incoming call.
+         * This notification will have a decline and an answer action, will allow a single
+         * custom {@link Builder#addAction(Action) action}, and will have a default
+         * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
+         *
+         * @param person        The person displayed as the caller.
+         *                      The person also needs to have a non-empty name associated with it.
+         * @param declineIntent The intent to be sent when the user taps the decline action
+         * @param answerIntent  The intent to be sent when the user taps the answer action
+         */
+        @NonNull
+        public static CallStyle forIncomingCall(@NonNull Person person,
+                @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
+            return new CallStyle(CALL_TYPE_INCOMING, person,
+                    null /* hangUpIntent */,
+                    requireNonNull(declineIntent, "declineIntent is required"),
+                    requireNonNull(answerIntent, "answerIntent is required")
+            );
+        }
+
+        /**
+         * Create a CallStyle for an ongoing call.
+         * This notification will have a hang up action, will allow up to two
+         * custom {@link Builder#addAction(Action) actions}, and will have a default
+         * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
+         *
+         * @param person       The person displayed as being on the other end of the call.
+         *                     The person also needs to have a non-empty name associated with it.
+         * @param hangUpIntent The intent to be sent when the user taps the hang up action
+         */
+        @NonNull
+        public static CallStyle forOngoingCall(@NonNull Person person,
+                @NonNull PendingIntent hangUpIntent) {
+            return new CallStyle(CALL_TYPE_ONGOING, person,
+                    requireNonNull(hangUpIntent, "hangUpIntent is required"),
+                    null /* declineIntent */,
+                    null /* answerIntent */
+            );
+        }
+
+        /**
+         * Create a CallStyle for a call that is being screened.
+         * This notification will have a hang up and an answer action, will allow a single
+         * custom {@link Builder#addAction(Action) action}, and will have a default
+         * {@link Builder#setContentText(CharSequence) content text} for a call that is being
+         * screened.
+         *
+         * @param person       The person displayed as the caller.
+         *                     The person also needs to have a non-empty name associated with it.
+         * @param hangUpIntent The intent to be sent when the user taps the hang up action
+         * @param answerIntent The intent to be sent when the user taps the answer action
+         */
+        @NonNull
+        public static CallStyle forScreeningCall(@NonNull Person person,
+                @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
+            return new CallStyle(CALL_TYPE_SCREENING, person,
+                    requireNonNull(hangUpIntent, "hangUpIntent is required"),
+                    null /* declineIntent */,
+                    requireNonNull(answerIntent, "answerIntent is required")
+            );
+        }
+
+        /**
+         * @param person The person displayed for the incoming call.
+         *             The user also needs to have a non-empty name associated with it.
+         * @param hangUpIntent The intent to be sent when the user taps the hang up action
+         * @param declineIntent The intent to be sent when the user taps the decline action
+         * @param answerIntent The intent to be sent when the user taps the answer action
+         */
+        private CallStyle(int callType, @NonNull Person person,
+                @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
+                @Nullable PendingIntent answerIntent) {
+            if (person == null || TextUtils.isEmpty(person.getName())) {
+                throw new IllegalArgumentException("person must have a non-empty a name");
+            }
+            mCallType = callType;
+            mPerson = person;
+            mAnswerIntent = answerIntent;
+            mDeclineIntent = declineIntent;
+            mHangUpIntent = hangUpIntent;
+        }
+
+        /**
+         * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
+         * as a verification status of the caller.
+         */
+        @NonNull
+        public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
+            mVerificationIcon = verificationIcon;
+            return this;
+        }
+
+        /**
+         * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
+         * as a verification status of the caller.
+         */
+        @NonNull
+        public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
+            mVerificationText = safeCharSequence(verificationText);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean displayCustomViewInline() {
+            // This is a lie; True is returned to make sure that the custom view is not used
+            // instead of the template, but it will not actually be included.
+            return true;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void purgeResources() {
+            super.purgeResources();
+            if (mVerificationIcon != null) {
+                mVerificationIcon.convertToAshmem();
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void reduceImageSizes(Context context) {
+            super.reduceImageSizes(context);
+            if (mVerificationIcon != null) {
+                int rightIconSize = context.getResources().getDimensionPixelSize(
+                        ActivityManager.isLowRamDeviceStatic()
+                                ? R.dimen.notification_right_icon_size_low_ram
+                                : R.dimen.notification_right_icon_size);
+                mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeContentView(boolean increasedHeight) {
+            return makeCallLayout();
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+            return makeCallLayout();
+        }
+
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
+            return makeCallLayout();
+        }
+
+        @NonNull
+        private Action makeNegativeAction() {
+            if (mDeclineIntent == null) {
+                return makeAction(R.drawable.ic_call_decline,
+                        R.string.call_notification_hang_up_action,
+                        R.color.call_notification_decline_color, mHangUpIntent);
+            } else {
+                return makeAction(R.drawable.ic_call_decline,
+                        R.string.call_notification_decline_action,
+                        R.color.call_notification_decline_color, mDeclineIntent);
+            }
+        }
+
+        @Nullable
+        private Action makeAnswerAction() {
+            return mAnswerIntent == null ? null : makeAction(R.drawable.ic_call_answer,
+                    R.string.call_notification_answer_action,
+                    R.color.call_notification_answer_color, mAnswerIntent);
+        }
+
+        @NonNull
+        private Action makeAction(@DrawableRes int icon, @StringRes int title,
+                @ColorRes int colorRes, PendingIntent intent) {
+            Action action = new Action.Builder(Icon.createWithResource("", icon),
+                    new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
+                            new ForegroundColorSpan(mBuilder.mContext.getColor(colorRes)),
+                            SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
+                    intent).build();
+            action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
+            return action;
+        }
+
+        private ArrayList<Action> makeActionsList() {
+            final Action negativeAction = makeNegativeAction();
+            final Action answerAction = makeAnswerAction();
+
+            ArrayList<Action> actions = new ArrayList<>(MAX_ACTION_BUTTONS);
+            final Action lastAction;
+            if (answerAction == null) {
+                // If there's no answer action, put the hang up / decline action at the end
+                lastAction = negativeAction;
+            } else {
+                // Otherwise put the answer action at the end, and put the decline action at start.
+                actions.add(negativeAction);
+                lastAction = answerAction;
+            }
+            // For consistency with the standard actions bar, contextual actions are ignored.
+            for (Action action : Builder.filterOutContextualActions(mBuilder.mActions)) {
+                if (actions.size() >= MAX_ACTION_BUTTONS - 1) {
+                    break;
+                }
+                actions.add(action);
+            }
+            actions.add(lastAction);
+            return actions;
+        }
+
+        private RemoteViews makeCallLayout() {
+            Bundle extras = mBuilder.mN.extras;
+            CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
+            if (text == null) {
+                text = getDefaultText();
+            }
+
+            // Bind standard template
+            StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .allowActionIcons(true)
+                    .hideLargeIcon(true)
+                    .text(text)
+                    .summaryText(mBuilder.processLegacyText(mVerificationText));
+            // TODO(b/179178086): hide the snooze button
+            RemoteViews contentView = mBuilder.applyStandardTemplate(
+                    mBuilder.getCallLayoutResource(), p, null /* result */);
+
+            // Bind actions.
+            mBuilder.resetStandardTemplateWithActions(contentView);
+            bindCallActions(contentView, p);
+
+            // Bind some extra conversation-specific header fields.
+            mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+            mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+            contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+            bindCallerVerification(contentView, p);
+
+            // Bind some custom CallLayout properties
+            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+                    mBuilder.isColorized(p)
+                            ? mBuilder.getPrimaryTextColor(p)
+                            : mBuilder.resolveContrastColor(p));
+            contentView.setInt(R.id.status_bar_latest_event_content,
+                    "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p));
+            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+                    mBuilder.mN.mLargeIcon);
+            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+                    mBuilder.mN.extras);
+
+            return contentView;
+        }
+
+        private void bindCallActions(RemoteViews view, StandardTemplateParams p) {
+            view.setViewVisibility(R.id.actions_container, View.VISIBLE);
+            view.setViewVisibility(R.id.actions, View.VISIBLE);
+            view.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+                    RemoteViews.MARGIN_BOTTOM, 0);
+
+            // Clear view padding to allow buttons to start on the left edge.
+            // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
+            view.setViewPadding(R.id.actions, 0, 0, 0, 0);
+            // Add an optional indent that will make buttons start at the correct column when
+            // there is enough space to do so (and fall back to the left edge if not).
+            view.setInt(R.id.actions, "setCollapsibleIndentDimen",
+                    R.dimen.call_notification_collapsible_indent);
+
+            // Emphasize so that buttons have borders or colored backgrounds
+            boolean emphasizedMode = true;
+            view.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
+            // Use "wrap_content" (unlike normal emphasized mode) and allow prioritizing the
+            // required actions (Answer, Decline, and Hang Up).
+            view.setBoolean(R.id.actions, "setPrioritizedWrapMode", true);
+
+            // Create the buttons for the generated actions list.
+            int i = 0;
+            for (Action action : makeActionsList()) {
+                final RemoteViews button = mBuilder.generateActionButton(action, emphasizedMode, p);
+                if (i > 0) {
+                    // Clear start margin from non-first buttons to reduce the gap between buttons.
+                    // (8dp remaining gap is from all buttons' standard 4dp inset).
+                    button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
+                }
+                view.addView(R.id.actions, button);
+                ++i;
+            }
+        }
+
+        private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
+            if (mVerificationIcon != null) {
+                contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
+                contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
+                        mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
+                contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
+            } else {
+                contentView.setViewVisibility(R.id.verification_icon, View.GONE);
+            }
+            if (!TextUtils.isEmpty(mVerificationText)) {
+                contentView.setTextViewText(R.id.verification_text, mVerificationText);
+                mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
+                contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
+            } else {
+                contentView.setViewVisibility(R.id.verification_text, View.GONE);
+            }
+        }
+
+        @Nullable
+        private String getDefaultText() {
+            switch (mCallType) {
+                case CALL_TYPE_INCOMING:
+                    return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
+                case CALL_TYPE_ONGOING:
+                    return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
+                case CALL_TYPE_SCREENING:
+                    return mBuilder.mContext.getString(R.string.call_notification_screening_text);
+            }
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        public void addExtras(Bundle extras) {
+            super.addExtras(extras);
+            extras.putInt(EXTRA_CALL_TYPE, mCallType);
+            extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
+            if (mVerificationIcon != null) {
+                extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
+            }
+            if (mVerificationText != null) {
+                extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
+            }
+            if (mAnswerIntent != null) {
+                extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
+            }
+            if (mDeclineIntent != null) {
+                extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
+            }
+            if (mHangUpIntent != null) {
+                extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
+            }
+            fixTitleAndTextExtras(extras);
+        }
+
+        private void fixTitleAndTextExtras(Bundle extras) {
+            CharSequence sender = mPerson != null ? mPerson.getName() : null;
+            if (sender != null) {
+                extras.putCharSequence(EXTRA_TITLE, sender);
+            }
+            if (extras.getCharSequence(EXTRA_TEXT) == null) {
+                extras.putCharSequence(EXTRA_TEXT, getDefaultText());
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        protected void restoreFromExtras(Bundle extras) {
+            super.restoreFromExtras(extras);
+            mCallType = extras.getInt(EXTRA_CALL_TYPE);
+            mPerson = extras.getParcelable(EXTRA_CALL_PERSON);
+            mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
+            mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
+            mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT);
+            mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT);
+            mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean hasSummaryInHeader() {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean areNotificationsVisiblyDifferent(Style other) {
+            if (other == null || getClass() != other.getClass()) {
+                return true;
+            }
+            CallStyle otherS = (CallStyle) other;
+            return !Objects.equals(mCallType, otherS.mCallType)
+                    || !Objects.equals(mPerson, otherS.mPerson)
+                    || !Objects.equals(mVerificationText, otherS.mVerificationText);
+        }
+    }
+
     /**
      * Notification style for custom views that are decorated by the system
      *
@@ -9474,6 +9972,15 @@
              * <p>The shortcut activity will be used when the bubble is expanded. This will display
              * the shortcut activity in a floating window over the existing foreground activity.</p>
              *
+             * <p>When the shortcut is displayed in a bubble, there will be an intent
+             * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+             * with {@code true}. You may check this in the onCreate of your activity via:
+             *
+             * <pre class="prettyprint">
+             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+             * </pre>
+             * </p>
+             *
              * <p>If the shortcut has not been published when the bubble notification is sent,
              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
              * the bubble will be removed.</p>
@@ -9502,6 +10009,15 @@
              * app content in a floating window over the existing foreground activity. The intent
              * should point to a resizable activity. </p>
              *
+             * <p>When the activity is displayed in a bubble, there will be an intent
+             * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+             * with {@code true}. You may check this in the onCreate of your activity via:
+             *
+             * <pre class="prettyprint">
+             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+             * </pre>
+             * </p>
+             *
              * @throws NullPointerException if intent is null.
              * @throws NullPointerException if icon is null.
              */
@@ -11376,6 +11892,7 @@
         boolean mHideActions;
         boolean mHideProgress;
         boolean mPromotePicture;
+        boolean mAllowActionIcons;
         CharSequence title;
         CharSequence text;
         CharSequence headerTextSecondary;
@@ -11392,6 +11909,7 @@
             mHideActions = false;
             mHideProgress = false;
             mPromotePicture = false;
+            mAllowActionIcons = false;
             title = null;
             text = null;
             summaryText = null;
@@ -11431,6 +11949,11 @@
             return this;
         }
 
+        final StandardTemplateParams allowActionIcons(boolean allowActionIcons) {
+            this.mAllowActionIcons = allowActionIcons;
+            return this;
+        }
+
         final StandardTemplateParams promotePicture(boolean promotePicture) {
             this.mPromotePicture = promotePicture;
             return this;
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 390d921..938ce0d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -53,13 +53,6 @@
     public int userId;
 
     /**
-     * The id of the ActivityStack that currently contains this task.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int stackId;
-
-    /**
      * The identifier for this task.
      */
     public int taskId;
@@ -358,7 +351,6 @@
      */
     void readFromParcel(Parcel source) {
         userId = source.readInt();
-        stackId = source.readInt();
         taskId = source.readInt();
         displayId = source.readInt();
         isRunning = source.readBoolean();
@@ -394,7 +386,6 @@
      */
     void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(userId);
-        dest.writeInt(stackId);
         dest.writeInt(taskId);
         dest.writeInt(displayId);
         dest.writeBoolean(isRunning);
@@ -428,7 +419,7 @@
 
     @Override
     public String toString() {
-        return "TaskInfo{userId=" + userId + " stackId=" + stackId + " taskId=" + taskId
+        return "TaskInfo{userId=" + userId + " taskId=" + taskId
                 + " displayId=" + displayId
                 + " isRunning=" + isRunning
                 + " baseIntent=" + baseIntent + " baseActivity=" + baseActivity
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 5e1cbad..9eb9a7b 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.stats.devicepolicy.DevicePolicyEnums;
 
 import java.util.Locale;
 
@@ -35,6 +36,13 @@
  */
 @TestApi
 public final class FullyManagedDeviceProvisioningParams implements Parcelable {
+    private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
+            "LEAVE_ALL_SYSTEM_APPS_ENABLED";
+    private static final String CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS_PARAM =
+            "CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS";
+    private static final String TIME_ZONE_PROVIDED_PARAM = "TIME_ZONE_PROVIDED";
+    private static final String LOCALE_PROVIDED_PARAM = "LOCALE_PROVIDED";
+
     @NonNull private final ComponentName mDeviceAdminComponentName;
     @NonNull private final String mOwnerName;
     private final boolean mLeaveAllSystemAppsEnabled;
@@ -121,6 +129,29 @@
     }
 
     /**
+     * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+     */
+    public void logParams(@NonNull String callerPackage) {
+        requireNonNull(callerPackage);
+
+        logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
+        logParam(callerPackage, CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS_PARAM,
+                mDeviceOwnerCanGrantSensorsPermissions);
+        logParam(callerPackage, TIME_ZONE_PROVIDED_PARAM, /* value= */ mTimeZone != null);
+        logParam(callerPackage, LOCALE_PROVIDED_PARAM, /* value= */ mLocale != null);
+    }
+
+    private void logParam(String callerPackage, String param, boolean value) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_PARAM)
+                .setStrings(callerPackage)
+                .setAdmin(mDeviceAdminComponentName)
+                .setStrings(param)
+                .setBoolean(value)
+                .write();
+    }
+
+    /**
      * Builder class for {@link FullyManagedDeviceProvisioningParams} objects.
      */
     public static final class Builder {
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index 5fe63d1..1a6099a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.stats.devicepolicy.DevicePolicyEnums;
 
 /**
  * Params required to provision a managed profile, see
@@ -34,6 +35,13 @@
  */
 @TestApi
 public final class ManagedProfileProvisioningParams implements Parcelable {
+    private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
+            "LEAVE_ALL_SYSTEM_APPS_ENABLED";
+    private static final String ORGANIZATION_OWNED_PROVISIONING_PARAM =
+            "ORGANIZATION_OWNED_PROVISIONING";
+    private static final String ACCOUNT_TO_MIGRATE_PROVIDED_PARAM = "ACCOUNT_TO_MIGRATE_PROVIDED";
+    private static final String KEEP_MIGRATED_ACCOUNT_PARAM = "KEEP_MIGRATED_ACCOUNT";
+
     @NonNull private final ComponentName mProfileAdminComponentName;
     @NonNull private final String mOwnerName;
     @Nullable private final String mProfileName;
@@ -93,6 +101,30 @@
     }
 
     /**
+     * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+     */
+    public void logParams(@NonNull String callerPackage) {
+        requireNonNull(callerPackage);
+
+        logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
+        logParam(callerPackage, ORGANIZATION_OWNED_PROVISIONING_PARAM,
+                mOrganizationOwnedProvisioning);
+        logParam(callerPackage, KEEP_MIGRATED_ACCOUNT_PARAM, mKeepAccountMigrated);
+        logParam(callerPackage, ACCOUNT_TO_MIGRATE_PROVIDED_PARAM,
+                /* value= */ mAccountToMigrate != null);
+    }
+
+    private void logParam(String callerPackage, String param, boolean value) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_PARAM)
+                .setStrings(callerPackage)
+                .setAdmin(mProfileAdminComponentName)
+                .setStrings(param)
+                .setBoolean(value)
+                .write();
+    }
+
+    /**
      * Builder class for {@link ManagedProfileProvisioningParams} objects.
      */
     public static final class Builder {
diff --git a/core/java/android/app/compat/OWNERS b/core/java/android/app/compat/OWNERS
new file mode 100644
index 0000000..f8c3520
--- /dev/null
+++ b/core/java/android/app/compat/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/compat/OWNERS
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
index 1e0653d..920b9fe 100644
--- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -138,6 +138,15 @@
         dest.writeInt(mEventType);
     }
 
+    @Override
+    public String toString() {
+        return "SmartspaceTargetEvent{"
+                + "mSmartspaceTarget=" + mSmartspaceTarget
+                + ", mSmartspaceActionId='" + mSmartspaceActionId + '\''
+                + ", mEventType=" + mEventType
+                + '}';
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 2c1e951..30ea5c4 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -30,7 +30,7 @@
 interface IUsageStatsManager {
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     ParceledListSlice queryUsageStats(int bucketType, long beginTime, long endTime,
-            String callingPackage);
+            String callingPackage, int userId);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime,
             String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index f74d16e..31781ec 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -437,11 +438,12 @@
      * @see #INTERVAL_YEARLY
      * @see #INTERVAL_BEST
      */
+    @UserHandleAware
     public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
         try {
             @SuppressWarnings("unchecked")
             ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
-                    endTime, mContext.getOpPackageName());
+                    endTime, mContext.getOpPackageName(), mContext.getUserId());
             if (slice != null) {
                 return slice.getList();
             }
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
index 960a087..9007d9d 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.util.DataClass;
 
+import java.util.Date;
 import java.util.Objects;
 
 /**
@@ -39,12 +40,19 @@
     private final @NonNull String mPackageName;
     private final @Nullable String mDeviceProfile;
     private final boolean mNotifyOnDeviceNearby;
+    private final long mTimeApprovedMs;
 
     /** @hide */
     public int getUserId() {
         return mUserId;
     }
 
+    private String timeApprovedMsToString() {
+        return new Date(mTimeApprovedMs).toString();
+    }
+
+
+
 
 
     // Code below generated by codegen v1.0.22.
@@ -71,7 +79,8 @@
             @NonNull String deviceMacAddress,
             @NonNull String packageName,
             @Nullable String deviceProfile,
-            boolean notifyOnDeviceNearby) {
+            boolean notifyOnDeviceNearby,
+            long timeApprovedMs) {
         this.mUserId = userId;
         com.android.internal.util.AnnotationValidations.validate(
                 UserIdInt.class, null, mUserId);
@@ -83,6 +92,7 @@
                 NonNull.class, null, mPackageName);
         this.mDeviceProfile = deviceProfile;
         this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+        this.mTimeApprovedMs = timeApprovedMs;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -107,6 +117,11 @@
         return mNotifyOnDeviceNearby;
     }
 
+    @DataClass.Generated.Member
+    public long getTimeApprovedMs() {
+        return mTimeApprovedMs;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -118,7 +133,8 @@
                 "deviceMacAddress = " + mDeviceMacAddress + ", " +
                 "packageName = " + mPackageName + ", " +
                 "deviceProfile = " + mDeviceProfile + ", " +
-                "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby +
+                "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby + ", " +
+                "timeApprovedMs = " + timeApprovedMsToString() +
         " }";
     }
 
@@ -139,7 +155,8 @@
                 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
                 && Objects.equals(mPackageName, that.mPackageName)
                 && Objects.equals(mDeviceProfile, that.mDeviceProfile)
-                && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby;
+                && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
+                && mTimeApprovedMs == that.mTimeApprovedMs;
     }
 
     @Override
@@ -154,6 +171,7 @@
         _hash = 31 * _hash + Objects.hashCode(mPackageName);
         _hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
         _hash = 31 * _hash + Boolean.hashCode(mNotifyOnDeviceNearby);
+        _hash = 31 * _hash + Long.hashCode(mTimeApprovedMs);
         return _hash;
     }
 
@@ -171,6 +189,7 @@
         dest.writeString(mDeviceMacAddress);
         dest.writeString(mPackageName);
         if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
+        dest.writeLong(mTimeApprovedMs);
     }
 
     @Override
@@ -190,6 +209,7 @@
         String deviceMacAddress = in.readString();
         String packageName = in.readString();
         String deviceProfile = (flg & 0x8) == 0 ? null : in.readString();
+        long timeApprovedMs = in.readLong();
 
         this.mUserId = userId;
         com.android.internal.util.AnnotationValidations.validate(
@@ -202,6 +222,7 @@
                 NonNull.class, null, mPackageName);
         this.mDeviceProfile = deviceProfile;
         this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+        this.mTimeApprovedMs = timeApprovedMs;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -221,10 +242,10 @@
     };
 
     @DataClass.Generated(
-            time = 1610482674799L,
+            time = 1612832377589L,
             codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/companion/Association.java",
-            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final  boolean mNotifyOnDeviceNearby\npublic  int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final  boolean mNotifyOnDeviceNearby\nprivate final  long mTimeApprovedMs\npublic  int getUserId()\nprivate  java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 527d8df..95d3515 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -49,4 +49,6 @@
     void registerDevicePresenceListenerService(in String packageName, in String deviceAddress);
 
     void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress);
+
+    boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index d7dc86a..46d8900 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -63,7 +63,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.system.Int32Ref;
+import android.system.Int64Ref;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
@@ -4050,7 +4050,7 @@
         // Convert to Point, since that's what the API is defined as
         final Bundle opts = new Bundle();
         opts.putParcelable(EXTRA_SIZE, new Point(size.getWidth(), size.getHeight()));
-        final Int32Ref orientation = new Int32Ref(0);
+        final Int64Ref orientation = new Int64Ref(0);
 
         Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> {
             final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30b2404..4abd8cd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5718,7 +5718,7 @@
     public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
 
     /**
-     * Used in the extra field in the remote intent. It's astring token passed with the
+     * Used in the extra field in the remote intent. It's a string token passed with the
      * remote intent.
      */
     public static final String EXTRA_REMOTE_INTENT_TOKEN =
@@ -6062,6 +6062,16 @@
      */
     public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
 
+    /**
+     * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or
+     * pending intent provided for the bubble. If the extra is not present or false, then it is not
+     * bubbled.
+     *
+     * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata)
+     * @see android.app.Notification.BubbleMetadata.Builder#Builder(String)
+     */
+    public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/om/CriticalOverlayInfo.java b/core/java/android/content/om/CriticalOverlayInfo.java
new file mode 100644
index 0000000..8fbc698
--- /dev/null
+++ b/core/java/android/content/om/CriticalOverlayInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * A subset of {@link OverlayInfo} fields that when changed cause the overlay's settings to be
+ * completely reinitialized.
+ *
+ * @hide
+ */
+public interface CriticalOverlayInfo {
+
+    /**
+     * @return the package name of the overlay.
+     */
+    @NonNull
+    String getPackageName();
+
+    /**
+     * @return the unique name of the overlay within its containing package.
+     */
+    @Nullable
+    String getOverlayName();
+
+    /**
+     * @return the target package name of the overlay.
+     */
+    @NonNull
+    String getTargetPackageName();
+
+    /**
+     * @return the name of the target overlayable declaration.
+     */
+    @Nullable
+    String getTargetOverlayableName();
+
+    /**
+     * @return an identifier representing the current overlay.
+     */
+    @NonNull
+    OverlayIdentifier getOverlayIdentifier();
+
+    /**
+     * Returns whether or not the overlay is a {@link FabricatedOverlay}.
+     */
+    boolean isFabricated();
+}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
new file mode 100644
index 0000000..d62b47b
--- /dev/null
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
+ *
+ * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
+ * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
+ * overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
+ * system partition would fulfil the {@code system} and {@code signature} policies.
+ *
+ * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
+ * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
+ *
+ * Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
+ * overlays and fabricated overlays.
+ * @hide
+ */
+public class FabricatedOverlay {
+
+    /** Retrieves the identifier for this fabricated overlay. */
+    public OverlayIdentifier getIdentifier() {
+        return new OverlayIdentifier(
+                mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
+    }
+
+    public static class Builder {
+        private final String mOwningPackage;
+        private final String mName;
+        private final String mTargetPackage;
+        private String mTargetOverlayable = "";
+        private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>();
+
+        /**
+         * Constructs a build for a fabricated overlay.
+         *
+         * @param owningPackage the name of the package that owns the fabricated overlay (must
+         *                      be a package name of this UID).
+         * @param name a name used to uniquely identify the fabricated overlay owned by
+         *             {@param owningPackageName}
+         * @param targetPackage the name of the package to overlay
+         */
+        public Builder(@NonNull String owningPackage, @NonNull String name,
+                @NonNull String targetPackage) {
+            Preconditions.checkStringNotEmpty(owningPackage,
+                    "'owningPackage' must not be empty nor null");
+            Preconditions.checkStringNotEmpty(name,
+                    "'name'' must not be empty nor null");
+            Preconditions.checkStringNotEmpty(targetPackage,
+                    "'targetPackage' must not be empty nor null");
+
+            mOwningPackage = owningPackage;
+            mName = name;
+            mTargetPackage = targetPackage;
+        }
+
+        /**
+         * Sets the name of the overlayable resources to overlay (can be null).
+         */
+        public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
+            mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+            return this;
+        }
+
+        /**
+         * Sets the value of
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *                     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         *
+         * @see android.util.TypedValue#type
+         */
+        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.dataType = dataType;
+            entry.data = value;
+            mEntries.add(entry);
+            return this;
+        }
+
+        /** Builds an immutable fabricated overlay. */
+        public FabricatedOverlay build() {
+            final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
+            overlay.packageName = mOwningPackage;
+            overlay.overlayName = mName;
+            overlay.targetPackageName = mTargetPackage;
+            overlay.targetOverlayable = mTargetOverlayable;
+            overlay.entries = new ArrayList<>();
+            overlay.entries.addAll(mEntries);
+            return new FabricatedOverlay(overlay);
+        }
+    }
+
+    final FabricatedOverlayInternal mOverlay;
+    private FabricatedOverlay(FabricatedOverlayInternal overlay) {
+        mOverlay = overlay;
+    }
+}
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index 0b950b4..e319d2c 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -16,6 +16,7 @@
 
 package android.content.om;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManagerTransaction;
 
@@ -66,6 +67,17 @@
     OverlayInfo getOverlayInfo(in String packageName, in int userId);
 
     /**
+     * Returns information about the overlay with the given package name for the
+     * specified user.
+     *
+     * @param packageName The name of the overlay package.
+     * @param userId The user to get the OverlayInfo for.
+     * @return The OverlayInfo for the overlay package; or null if no such
+     *         overlay package exists.
+     */
+    OverlayInfo getOverlayInfoByIdentifier(in OverlayIdentifier packageName, in int userId);
+
+    /**
      * Request that an overlay package be enabled or disabled when possible to
      * do so.
      *
@@ -163,7 +175,7 @@
      * Invalidates and removes the idmap for an overlay,
      * @param packageName The name of the overlay package whose idmap should be deleted.
      */
-    void invalidateCachesForOverlay(in String packageName, in int userIs);
+    void invalidateCachesForOverlay(in String packageName, in int userId);
 
     /**
      * Perform a series of requests related to overlay packages. This is an
diff --git a/tools/hiddenapi/Android.bp b/core/java/android/content/om/OverlayIdentifier.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to core/java/android/content/om/OverlayIdentifier.aidl
index e0eb06cb..d1c7770 100644
--- a/tools/hiddenapi/Android.bp
+++ b/core/java/android/content/om/OverlayIdentifier.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,17 +14,6 @@
  * limitations under the License.
  */
 
-python_binary_host {
-	name: "merge_csv",
-	main: "merge_csv.py",
-	srcs: ["merge_csv.py"],
-	version: {
-		py2: {
-			enabled: false,
-		},
-		py3: {
-			enabled: true,
-			embedded_launcher: true
-		},
-	},
-}
+package android.content.om;
+
+parcelable OverlayIdentifier;
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
new file mode 100644
index 0000000..454d0d1
--- /dev/null
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A key used to uniquely identify a Runtime Resource Overlay (RRO).
+ *
+ * An overlay always belongs to a package and may optionally have a name associated with it.
+ * The name helps uniquely identify a particular overlay within a package.
+ * @hide
+ */
+/** @hide */
+@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
+        genEqualsHashCode = true, genToString = false)
+public class OverlayIdentifier implements Parcelable  {
+    /**
+     * The package name containing or owning the overlay.
+     */
+    @Nullable
+    private final String mPackageName;
+
+    /**
+     * The unique name within the package of the overlay.
+     */
+    @Nullable
+    private final String mOverlayName;
+
+    /**
+     * Creates an identifier from a package and unique name within the package.
+     *
+     * @param packageName the package containing or owning the overlay
+     * @param overlayName the unique name of the overlay within the package
+     */
+    public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
+        mPackageName = packageName;
+        mOverlayName = overlayName;
+    }
+
+    /**
+     * Creates an identifier for an overlay without a name.
+     *
+     * @param packageName the package containing or owning the overlay
+     */
+    public OverlayIdentifier(@NonNull String packageName) {
+        mPackageName = packageName;
+        mOverlayName = null;
+    }
+
+    @Override
+    public String toString() {
+        return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
+    }
+
+    /** @hide */
+    public static OverlayIdentifier fromString(@NonNull String text) {
+        final String[] parts = text.split(":", 2);
+        if (parts.length == 2) {
+            return new OverlayIdentifier(parts[0], parts[1]);
+        } else {
+            return new OverlayIdentifier(parts[0]);
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayIdentifier.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Retrieves the package name containing or owning the overlay.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Retrieves the unique name within the package of the overlay.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getOverlayName() {
+        return mOverlayName;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(OverlayIdentifier other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        OverlayIdentifier that = (OverlayIdentifier) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mOverlayName, that.mOverlayName);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + Objects.hashCode(mOverlayName);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mPackageName != null) flg |= 0x1;
+        if (mOverlayName != null) flg |= 0x2;
+        dest.writeByte(flg);
+        if (mPackageName != null) dest.writeString(mPackageName);
+        if (mOverlayName != null) dest.writeString(mOverlayName);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected OverlayIdentifier(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String packageName = (flg & 0x1) == 0 ? null : in.readString();
+        String overlayName = (flg & 0x2) == 0 ? null : in.readString();
+
+        this.mPackageName = packageName;
+        this.mOverlayName = overlayName;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<OverlayIdentifier> CREATOR
+            = new Parcelable.Creator<OverlayIdentifier>() {
+        @Override
+        public OverlayIdentifier[] newArray(int size) {
+            return new OverlayIdentifier[size];
+        }
+
+        @Override
+        public OverlayIdentifier createFromParcel(@NonNull Parcel in) {
+            return new OverlayIdentifier(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1612482438728L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static  android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 517e4bd..c66f49c 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -26,6 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -37,7 +39,7 @@
  * @hide
  */
 @SystemApi
-public final class OverlayInfo implements Parcelable {
+public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
 
     /** @hide */
     @IntDef(prefix = "STATE_", value = {
@@ -143,6 +145,14 @@
     public final String packageName;
 
     /**
+     * The unique name within the package of the overlay.
+     *
+     * @hide
+     */
+    @Nullable
+    public final String overlayName;
+
+    /**
      * Package name of the target package
      *
      * @hide
@@ -201,6 +211,14 @@
      */
     public final boolean isMutable;
 
+    private OverlayIdentifier mIdentifierCached;
+
+    /**
+     *
+     * @hide
+     */
+    public final boolean isFabricated;
+
     /**
      * Create a new OverlayInfo based on source with an updated state.
      *
@@ -210,17 +228,28 @@
      * @hide
      */
     public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
-        this(source.packageName, source.targetPackageName, source.targetOverlayableName,
-                source.category, source.baseCodePath, state, source.userId, source.priority,
-                source.isMutable);
+        this(source.packageName, source.overlayName, source.targetPackageName,
+                source.targetOverlayableName, source.category, source.baseCodePath, state,
+                source.userId, source.priority, source.isMutable, source.isFabricated);
     }
 
     /** @hide */
+    @VisibleForTesting
     public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
             @Nullable String targetOverlayableName, @Nullable String category,
-            @NonNull String baseCodePath, int state, int userId,
-            int priority, boolean isMutable) {
+            @NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable) {
+        this(packageName, null /* overlayName */, targetPackageName, targetOverlayableName,
+                category, baseCodePath, state, userId, priority, isMutable,
+                false /* isFabricated */);
+    }
+
+    /** @hide */
+    public OverlayInfo(@NonNull String packageName, @Nullable String overlayName,
+            @NonNull String targetPackageName, @Nullable String targetOverlayableName,
+            @Nullable String category, @NonNull String baseCodePath, int state, int userId,
+            int priority, boolean isMutable, boolean isFabricated) {
         this.packageName = packageName;
+        this.overlayName = overlayName;
         this.targetPackageName = targetPackageName;
         this.targetOverlayableName = targetOverlayableName;
         this.category = category;
@@ -229,12 +258,14 @@
         this.userId = userId;
         this.priority = priority;
         this.isMutable = isMutable;
+        this.isFabricated = isFabricated;
         ensureValidState();
     }
 
     /** @hide */
     public OverlayInfo(Parcel source) {
         packageName = source.readString();
+        overlayName = source.readString();
         targetPackageName = source.readString();
         targetOverlayableName = source.readString();
         category = source.readString();
@@ -243,13 +274,15 @@
         userId = source.readInt();
         priority = source.readInt();
         isMutable = source.readBoolean();
+        isFabricated = source.readBoolean();
         ensureValidState();
     }
 
     /**
-     * Returns package name of the current overlay.
+     * {@inheritDoc}
      * @hide
      */
+    @Override
     @SystemApi
     @NonNull
     public String getPackageName() {
@@ -257,9 +290,20 @@
     }
 
     /**
-     * Returns the target package name of the current overlay.
+     * {@inheritDoc}
      * @hide
      */
+    @Override
+    @Nullable
+    public String getOverlayName() {
+        return overlayName;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
     @SystemApi
     @NonNull
     public String getTargetPackageName() {
@@ -268,7 +312,8 @@
 
     /**
      * Returns the category of the current overlay.
-     * @hide\
+     *
+     * @hide
      */
     @SystemApi
     @Nullable
@@ -278,6 +323,7 @@
 
     /**
      * Returns user handle for which this overlay applies to.
+     *
      * @hide
      */
     @SystemApi
@@ -287,15 +333,47 @@
     }
 
     /**
-     * Returns name of the target overlayable declaration.
+     * {@inheritDoc}
      * @hide
      */
+    @Override
     @SystemApi
     @Nullable
     public String getTargetOverlayableName() {
         return targetOverlayableName;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
+    public boolean isFabricated() {
+        return isFabricated;
+    }
+
+    /**
+     * Full path to the base APK or fabricated overlay for this overlay package.
+     *
+     * @hide
+     */
+    public String getBaseCodePath() {
+        return baseCodePath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
+    @NonNull
+    public OverlayIdentifier getOverlayIdentifier() {
+        if (mIdentifierCached == null) {
+            mIdentifierCached = new OverlayIdentifier(packageName, overlayName);
+        }
+        return mIdentifierCached;
+    }
+
     @SuppressWarnings("ConstantConditions")
     private void ensureValidState() {
         if (packageName == null) {
@@ -330,6 +408,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(packageName);
+        dest.writeString(overlayName);
         dest.writeString(targetPackageName);
         dest.writeString(targetOverlayableName);
         dest.writeString(category);
@@ -338,6 +417,7 @@
         dest.writeInt(userId);
         dest.writeInt(priority);
         dest.writeBoolean(isMutable);
+        dest.writeBoolean(isFabricated);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
@@ -410,6 +490,7 @@
         result = prime * result + userId;
         result = prime * result + state;
         result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
+        result = prime * result + ((overlayName == null) ? 0 : overlayName.hashCode());
         result = prime * result + ((targetPackageName == null) ? 0 : targetPackageName.hashCode());
         result = prime * result + ((targetOverlayableName == null) ? 0
                 : targetOverlayableName.hashCode());
@@ -439,6 +520,9 @@
         if (!packageName.equals(other.packageName)) {
             return false;
         }
+        if (!Objects.equals(overlayName, other.overlayName)) {
+            return false;
+        }
         if (!targetPackageName.equals(other.targetPackageName)) {
             return false;
         }
@@ -457,9 +541,13 @@
     @NonNull
     @Override
     public String toString() {
-        return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
-                + ((targetOverlayableName == null) ? ""
-                : ", targetOverlayable=" + targetOverlayableName)
-                + ", state=" + state + " (" + stateToString(state) + "), userId=" + userId + " }";
+        return "OverlayInfo {"
+                + "packageName=" + packageName
+                + ", overlayName=" + overlayName
+                + ", targetPackage=" + targetPackageName
+                + ", targetOverlayable=" + targetOverlayableName
+                + ", state=" + state + " (" + stateToString(state) + "),"
+                + ", userId=" + userId
+                + " }";
     }
 }
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7c14c28..0f7e01b 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -205,6 +205,25 @@
     }
 
     /**
+     * Returns information about the overlay represented by the identifier for the specified user.
+     *
+     * @param overlay the identifier representing the overlay
+     * @param userHandle the user of which to get overlay state info
+     * @return the overlay info or null if the overlay cannot be found
+     *
+     * @hide
+     */
+    @Nullable
+    public OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay,
+            @NonNull final UserHandle userHandle) {
+        try {
+            return mService.getOverlayInfoByIdentifier(overlay, userHandle.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns information about all overlays for the given target package for
      * the specified user. The returned list is ordered according to the
      * overlay priority with the highest priority at the end of the list.
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 1fa8973..73be0ff 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,6 +20,9 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -29,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Container for a batch of requests to the OverlayManagerService.
@@ -60,12 +64,13 @@
 
     private OverlayManagerTransaction(@NonNull final Parcel source) {
         final int size = source.readInt();
-        mRequests = new ArrayList<Request>(size);
+        mRequests = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final int request = source.readInt();
-            final String packageName = source.readString();
+            final OverlayIdentifier overlay = source.readParcelable(null);
             final int userId = source.readInt();
-            mRequests.add(new Request(request, packageName, userId));
+            final Bundle extras = source.readBundle(null);
+            mRequests.add(new Request(request, overlay, userId, extras));
         }
     }
 
@@ -95,22 +100,36 @@
 
         public static final int TYPE_SET_ENABLED = 0;
         public static final int TYPE_SET_DISABLED = 1;
+        public static final int TYPE_REGISTER_FABRICATED = 2;
+        public static final int TYPE_UNREGISTER_FABRICATED = 3;
 
-        @RequestType public final int type;
-        public final String packageName;
+        public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
+
+        @RequestType
+        public final int type;
+        @NonNull
+        public final OverlayIdentifier overlay;
         public final int userId;
+        @Nullable
+        public final Bundle extras;
 
-        public Request(@RequestType final int type, @NonNull final String packageName,
+        public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
                 final int userId) {
+            this(type, overlay, userId, null /* extras */);
+        }
+
+        public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
+                final int userId, @Nullable Bundle extras) {
             this.type = type;
-            this.packageName = packageName;
+            this.overlay = overlay;
             this.userId = userId;
+            this.extras = extras;
         }
 
         @Override
         public String toString() {
-            return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}",
-                    type, typeToString(), packageName, userId);
+            return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
+                    type, typeToString(), overlay, userId);
         }
 
         /**
@@ -123,6 +142,8 @@
             switch (type) {
                 case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
                 case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
+                case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
+                case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
                 default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
             }
         }
@@ -152,22 +173,56 @@
          * longer affect the resources of the target package. If the target is
          * currently running, its outdated resources will be replaced by new ones.
          *
-         * @param packageName The name of the overlay package.
+         * @param overlay The name of the overlay package.
          * @param enable true to enable the overlay, false to disable it.
          * @return this Builder object, so you can chain additional requests
          */
-        public Builder setEnabled(@NonNull String packageName, boolean enable) {
-            return setEnabled(packageName, enable, UserHandle.myUserId());
+        public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable) {
+            return setEnabled(overlay, enable, UserHandle.myUserId());
         }
 
         /**
          * @hide
          */
-        public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) {
-            checkNotNull(packageName);
+        public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) {
+            checkNotNull(overlay);
             @Request.RequestType final int type =
                 enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
-            mRequests.add(new Request(type, packageName, userId));
+            mRequests.add(new Request(type, overlay, userId));
+            return this;
+        }
+
+        /**
+         * Registers the fabricated overlay with the overlay manager so it can be enabled and
+         * disabled for any user.
+         *
+         * The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
+         * the existing overlay will be replaced by the newly registered overlay and the enabled
+         * state of the overlay will be left unchanged if the target package and target overlayable
+         * have not changed.
+         *
+         * @param overlay the overlay to register with the overlay manager
+         *
+         * @hide
+         */
+        public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+            final Bundle extras = new Bundle();
+            extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
+            mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
+                    UserHandle.USER_ALL, extras));
+            return this;
+        }
+
+        /**
+         * Disables and removes the overlay from the overlay manager for all users.
+         *
+         * @param overlay the overlay to disable and remove
+         *
+         * @hide
+         */
+        public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+            mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
+                    UserHandle.USER_ALL));
             return this;
         }
 
@@ -195,8 +250,9 @@
         for (int i = 0; i < size; i++) {
             final Request req = mRequests.get(i);
             dest.writeInt(req.type);
-            dest.writeString(req.packageName);
+            dest.writeParcelable(req.overlay, flags);
             dest.writeInt(req.userId);
+            dest.writeBundle(req.extras);
         }
     }
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0819d17..bf8d1f6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6151,6 +6151,56 @@
         }
 
         /**
+         * Returns whether this instance is currently signed, or has ever been signed, with a
+         * signing certificate from the provided {@link Set} of {@code certDigests}.
+         *
+         * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+         * of each trusted certificate with the digest characters in upper case. If this instance
+         * has multiple signers then all signers must be in the provided {@code Set}. If this
+         * instance has a signing lineage then this method will return true if any of the previous
+         * signers in the lineage match one of the entries in the {@code Set}.
+         */
+        public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
+            if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+                return false;
+            }
+            // If an app is signed by multiple signers then all of the signers must be in the Set.
+            if (signatures.length > 1) {
+                // If the Set has less elements than the number of signatures then immediately
+                // return false as there's no way to satisfy the requirement of all signatures being
+                // in the Set.
+                if (certDigests.size() < signatures.length) {
+                    return false;
+                }
+                for (Signature signature : signatures) {
+                    String signatureDigest = PackageUtils.computeSha256Digest(
+                            signature.toByteArray());
+                    if (!certDigests.contains(signatureDigest)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
+            if (certDigests.contains(signatureDigest)) {
+                return true;
+            }
+            if (hasPastSigningCertificates()) {
+                // The last element in the pastSigningCertificates array is the current signer;
+                // since that was verified above just check all the signers in the lineage.
+                for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+                    signatureDigest = PackageUtils.computeSha256Digest(
+                            pastSigningCertificates[i].toByteArray());
+                    if (certDigests.contains(signatureDigest)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        /**
          * Returns the SigningDetails with a descendant (or same) signer after verifying the
          * descendant has the same, a superset, or a subset of the lineage of the ancestor.
          *
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 35f02a8..a2e533a 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -30,6 +30,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
 
 /**
  * Information you can retrieve about a particular security permission
@@ -278,6 +279,15 @@
     @SystemApi
     public static final int PROTECTION_FLAG_ROLE = 0x4000000;
 
+    /**
+     * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
+     * of {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
             PROTECTION_FLAG_PRIVILEGED,
@@ -303,6 +313,7 @@
             PROTECTION_FLAG_RETAIL_DEMO,
             PROTECTION_FLAG_RECENTS,
             PROTECTION_FLAG_ROLE,
+            PROTECTION_FLAG_KNOWN_SIGNER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -466,6 +477,15 @@
      */
     public @Nullable CharSequence nonLocalizedDescription;
 
+    /**
+     * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
+     * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
+     * if the app is signed by any of these certificates.
+     *
+     * @hide
+     */
+    public @Nullable Set<String> knownCerts;
+
     /** @hide */
     public static int fixProtectionLevel(int level) {
         if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -570,6 +590,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
             protLevel.append("|role");
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
+            protLevel.append("|knownSigner");
+        }
         return protLevel.toString();
     }
 
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index fb0d904..9a84ded 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -655,6 +655,7 @@
         pi.protectionLevel = p.getProtectionLevel();
         pi.descriptionRes = p.getDescriptionRes();
         pi.flags = p.getFlags();
+        pi.knownCerts = p.getKnownCerts();
 
         if ((flags & PackageManager.GET_META_DATA) == 0) {
             return pi;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 66bdb9b..b7aa30f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2807,7 +2807,7 @@
      *                        limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
      * @return Success if it's valid.
      */
-    public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+    public static String validateName(String name, boolean requireSeparator,
             boolean requireFilename) {
         final int N = name.length();
         boolean hasSep = false;
@@ -2828,18 +2828,28 @@
                 front = true;
                 continue;
             }
-            return input.error("bad character '" + c + "'");
+            return "bad character '" + c + "'";
         }
         if (requireFilename) {
             if (!FileUtils.isValidExtFilename(name)) {
-                return input.error("Invalid filename");
+                return "Invalid filename";
             } else if (N > MAX_FILE_NAME_SIZE) {
-                return input.error("the length of the name is greater than " + MAX_FILE_NAME_SIZE);
+                return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
             }
         }
-        return hasSep || !requireSeparator
-                ? input.success(null)
-                : input.error("must have at least one '.' separator");
+        return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
+    }
+
+    /**
+     * @see #validateName(String, boolean, boolean)
+     */
+    public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+            boolean requireFilename) {
+        final String errorMessage = validateName(name, requireSeparator, requireFilename);
+        if (errorMessage != null) {
+            return input.error(errorMessage);
+        }
+        return input.success(null);
     }
 
     /**
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index f99a0b1..35bb33c 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -26,6 +26,8 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
+import java.util.Set;
+
 /** @hide */
 public class ParsedPermission extends ParsedComponent {
 
@@ -39,6 +41,8 @@
     boolean tree;
     @Nullable
     private ParsedPermissionGroup parsedPermissionGroup;
+    @Nullable
+    Set<String> knownCerts;
 
     @VisibleForTesting
     public ParsedPermission() {
@@ -81,6 +85,10 @@
         return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
     }
 
+    public @Nullable Set<String> getKnownCerts() {
+        return knownCerts;
+    }
+
     public int calculateFootprint() {
         int size = getName().length();
         if (getNonLocalizedLabel() != null) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce..a7cecbe 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -32,6 +33,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Locale;
+import java.util.Set;
 
 /** @hide */
 public class ParsedPermissionUtils {
@@ -90,6 +93,43 @@
             permission.flags = sa.getInt(
                     R.styleable.AndroidManifestPermission_permissionFlags, 0);
 
+            final int knownCertsResource = sa.getResourceId(
+                    R.styleable.AndroidManifestPermission_knownCerts, 0);
+            if (knownCertsResource != 0) {
+                // The knownCerts attribute supports both a string array resource as well as a
+                // string resource for the case where the permission should only be granted to a
+                // single known signer.
+                final String resourceType = res.getResourceTypeName(knownCertsResource);
+                if (resourceType.equals("array")) {
+                    final String[] knownCerts = res.getStringArray(knownCertsResource);
+                    if (knownCerts != null) {
+                        // Convert the provided digest to upper case for consistent Set membership
+                        // checks when verifying the signing certificate digests of requesting apps.
+                        permission.knownCerts = new ArraySet<>();
+                        for (String knownCert : knownCerts) {
+                            permission.knownCerts.add(knownCert.toUpperCase(Locale.US));
+                        }
+                    }
+                } else {
+                    final String knownCert = res.getString(knownCertsResource);
+                    if (knownCert != null) {
+                        permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                    }
+                }
+                if (permission.knownCerts == null) {
+                    Slog.w(TAG, packageName + " defines a knownSigner permission but"
+                            + " the provided knownCerts resource is null");
+                }
+            } else {
+                // If the knownCerts resource ID is null check if the app specified a string
+                // value for the attribute representing a single trusted signer.
+                final String knownCert = sa.getString(
+                        R.styleable.AndroidManifestPermission_knownCerts);
+                if (knownCert != null) {
+                    permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                }
+            }
+
             // For now only platform runtime permissions can be restricted
             if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
                 permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index db047f8..f551d6a 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -16,18 +16,33 @@
 
 package android.graphics.fonts;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.text.FontConfig;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Represents a font update request. Currently only font install request is supported.
  * @hide
  */
-// TODO: Support font config update.
 public final class FontUpdateRequest implements Parcelable {
 
+    public static final int TYPE_UPDATE_FONT_FILE = 0;
+    public static final int TYPE_UPDATE_FONT_FAMILY = 1;
+
+    @IntDef(prefix = "TYPE_", value = {
+            TYPE_UPDATE_FONT_FILE,
+            TYPE_UPDATE_FONT_FAMILY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
     public static final Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
         @Override
         public FontUpdateRequest createFromParcel(Parcel in) {
@@ -40,39 +55,67 @@
         }
     };
 
-    @NonNull
+    private final @Type int mType;
+    // NonNull if mType == TYPE_UPDATE_FONT_FILE.
+    @Nullable
     private final ParcelFileDescriptor mFd;
-    @NonNull
+    // NonNull if mType == TYPE_UPDATE_FONT_FILE.
+    @Nullable
     private final byte[] mSignature;
+    // NonNull if mType == TYPE_UPDATE_FONT_FAMILY.
+    @Nullable
+    private final FontConfig.FontFamily mFontFamily;
 
     public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
+        mType = TYPE_UPDATE_FONT_FILE;
         mFd = fd;
         mSignature = signature;
+        mFontFamily = null;
     }
 
-    private FontUpdateRequest(Parcel in) {
+    public FontUpdateRequest(@NonNull FontConfig.FontFamily fontFamily) {
+        mType = TYPE_UPDATE_FONT_FAMILY;
+        mFd = null;
+        mSignature = null;
+        mFontFamily = fontFamily;
+    }
+
+    protected FontUpdateRequest(Parcel in) {
+        mType = in.readInt();
         mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
         mSignature = in.readBlob();
+        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
     }
 
-    @NonNull
+    public @Type int getType() {
+        return mType;
+    }
+
+    @Nullable
     public ParcelFileDescriptor getFd() {
         return mFd;
     }
 
-    @NonNull
+    @Nullable
     public byte[] getSignature() {
         return mSignature;
     }
 
+    @Nullable
+    public FontConfig.FontFamily getFontFamily() {
+        return mFontFamily;
+    }
+
     @Override
     public int describeContents() {
-        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+        return mFd != null ? mFd.describeContents() : 0;
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
         dest.writeParcelable(mFd, flags);
         dest.writeBlob(mSignature);
+        dest.writeParcelable(mFontFamily, flags);
     }
 }
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 08b1e24..5f5697a 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -236,7 +236,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext,
+            return new BiometricTestSession(mContext, sensorId,
                     mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 2b68989..1c35608 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -25,6 +25,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.RemoteException;
 import android.util.ArraySet;
+import android.util.Log;
 
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
@@ -33,7 +34,10 @@
  */
 @TestApi
 public class BiometricTestSession implements AutoCloseable {
+    private static final String TAG = "BiometricTestSession";
+
     private final Context mContext;
+    private final int mSensorId;
     private final ITestSession mTestSession;
 
     // Keep track of users that were tested, which need to be cleaned up when finishing.
@@ -42,8 +46,10 @@
     /**
      * @hide
      */
-    public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
+    public BiometricTestSession(@NonNull Context context, int sensorId,
+            @NonNull ITestSession testSession) {
         mContext = context;
+        mSensorId = sensorId;
         mTestSession = testSession;
         mTestedUsers = new ArraySet<>();
         setTestHalEnabled(true);
@@ -61,6 +67,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     private void setTestHalEnabled(boolean enabled) {
         try {
+            Log.w(TAG, "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
             mTestSession.setTestHalEnabled(enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/tools/hiddenapi/Android.bp b/core/java/android/hardware/face/FaceAuthenticationFrame.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to core/java/android/hardware/face/FaceAuthenticationFrame.aidl
index e0eb06cb..4dc41f1 100644
--- a/tools/hiddenapi/Android.bp
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,18 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.face;
 
-python_binary_host {
-	name: "merge_csv",
-	main: "merge_csv.py",
-	srcs: ["merge_csv.py"],
-	version: {
-		py2: {
-			enabled: false,
-		},
-		py3: {
-			enabled: true,
-			embedded_launcher: true
-		},
-	},
-}
+/**
+ * @hide
+ */
+parcelable FaceAuthenticationFrame;
diff --git a/core/java/android/hardware/face/FaceDataFrame.java b/core/java/android/hardware/face/FaceDataFrame.java
index 3a0e09b..092359c 100644
--- a/core/java/android/hardware/face/FaceDataFrame.java
+++ b/core/java/android/hardware/face/FaceDataFrame.java
@@ -63,6 +63,22 @@
     }
 
     /**
+     * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}.
+     *
+     * @param acquiredInfo An integer corresponding to a known acquired message.
+     * @param vendorCode An integer representing a custom vendor-specific message. Ignored unless
+     *  {@code acquiredInfo} is {@code FACE_ACQUIRED_VENDOR}.
+     */
+    public FaceDataFrame(int acquiredInfo, int vendorCode) {
+        mAcquiredInfo = acquiredInfo;
+        mVendorCode = vendorCode;
+        mPan = 0f;
+        mTilt = 0f;
+        mDistance = 0f;
+        mIsCancellable = false;
+    }
+
+    /**
      * @return An integer corresponding to a known acquired message.
      *
      * @see android.hardware.biometrics.BiometricFaceConstants
diff --git a/tools/hiddenapi/Android.bp b/core/java/android/hardware/face/FaceEnrollFrame.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to core/java/android/hardware/face/FaceEnrollFrame.aidl
index e0eb06cb..b854681 100644
--- a/tools/hiddenapi/Android.bp
+++ b/core/java/android/hardware/face/FaceEnrollFrame.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,18 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.face;
 
-python_binary_host {
-	name: "merge_csv",
-	main: "merge_csv.py",
-	srcs: ["merge_csv.py"],
-	version: {
-		py2: {
-			enabled: false,
-		},
-		py3: {
-			enabled: true,
-			embedded_launcher: true
-		},
-	},
-}
+/**
+ * @hide
+ */
+parcelable FaceEnrollFrame;
diff --git a/core/java/android/hardware/face/FaceEnrollStage.java b/core/java/android/hardware/face/FaceEnrollStage.java
index 03dba55..de717fb 100644
--- a/core/java/android/hardware/face/FaceEnrollStage.java
+++ b/core/java/android/hardware/face/FaceEnrollStage.java
@@ -28,6 +28,7 @@
  */
 @Retention(RetentionPolicy.SOURCE)
 @IntDef({
+    FaceEnrollStage.UNKNOWN,
     FaceEnrollStage.FIRST_FRAME_RECEIVED,
     FaceEnrollStage.WAITING_FOR_CENTERING,
     FaceEnrollStage.HOLD_STILL_IN_CENTER,
@@ -37,6 +38,11 @@
 })
 public @interface FaceEnrollStage {
     /**
+     * The current enrollment stage is not known.
+     */
+    int UNKNOWN = -1;
+
+    /**
      * Enrollment has just begun. No action is needed from the user yet.
      */
     int FIRST_FRAME_RECEIVED = 0;
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 588bc01..f3da6a9 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -71,17 +71,19 @@
     private static final int MSG_FACE_DETECTED = 109;
     private static final int MSG_CHALLENGE_INTERRUPTED = 110;
     private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
+    private static final int MSG_AUTHENTICATION_FRAME = 112;
+    private static final int MSG_ENROLLMENT_FRAME = 113;
 
     private final IFaceService mService;
     private final Context mContext;
     private IBinder mToken = new Binder();
-    private AuthenticationCallback mAuthenticationCallback;
-    private FaceDetectionCallback mFaceDetectionCallback;
-    private EnrollmentCallback mEnrollmentCallback;
-    private RemovalCallback mRemovalCallback;
-    private SetFeatureCallback mSetFeatureCallback;
-    private GetFeatureCallback mGetFeatureCallback;
-    private GenerateChallengeCallback mGenerateChallengeCallback;
+    @Nullable private AuthenticationCallback mAuthenticationCallback;
+    @Nullable private FaceDetectionCallback mFaceDetectionCallback;
+    @Nullable private EnrollmentCallback mEnrollmentCallback;
+    @Nullable private RemovalCallback mRemovalCallback;
+    @Nullable private SetFeatureCallback mSetFeatureCallback;
+    @Nullable private GetFeatureCallback mGetFeatureCallback;
+    @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
     private CryptoObject mCryptoObject;
     private Face mRemovalFace;
     private Handler mHandler;
@@ -154,6 +156,16 @@
         public void onChallengeInterruptFinished(int sensorId) {
             mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
         }
+
+        @Override
+        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+        }
+
+        @Override
+        public void onEnrollmentFrame(FaceEnrollFrame frame) {
+            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+        }
     };
 
     /**
@@ -1248,6 +1260,12 @@
                 case MSG_CHALLENGE_INTERRUPT_FINISHED:
                     sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
                     break;
+                case MSG_AUTHENTICATION_FRAME:
+                    sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+                    break;
+                case MSG_ENROLLMENT_FRAME:
+                    sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+                    break;
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -1349,15 +1367,52 @@
 
     private void sendAcquiredResult(int acquireInfo, int vendorCode) {
         if (mAuthenticationCallback != null) {
+            final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+                    new FaceDataFrame(acquireInfo, vendorCode));
+            sendAuthenticationFrame(frame);
+        } else if (mEnrollmentCallback != null) {
+            final FaceEnrollFrame frame = new FaceEnrollFrame(
+                    null /* cell */,
+                    FaceEnrollStage.UNKNOWN,
+                    new FaceDataFrame(acquireInfo, vendorCode));
+            sendEnrollmentFrame(frame);
+        }
+    }
+
+    private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
+        if (frame == null) {
+            Slog.w(TAG, "Received null authentication frame");
+        } else if (mAuthenticationCallback != null) {
+            // TODO(b/178414967): Send additional frame data to callback
+            final int acquireInfo = frame.getData().getAcquiredInfo();
+            final int vendorCode = frame.getData().getVendorCode();
+            final int helpCode = getHelpCode(acquireInfo, vendorCode);
+            final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
             mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+            // Ensure that only non-null help messages are sent.
+            if (helpMessage != null) {
+                mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+            }
         }
-        final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
-        final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
-                ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
-        } else if (mAuthenticationCallback != null && msg != null) {
-            mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+    }
+
+    private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
+        if (frame == null) {
+            Slog.w(TAG, "Received null enrollment frame");
+        } else if (mEnrollmentCallback != null) {
+            // TODO(b/178414967): Send additional frame data to callback
+            final int acquireInfo = frame.getData().getAcquiredInfo();
+            final int vendorCode = frame.getData().getVendorCode();
+            final int helpCode = getHelpCode(acquireInfo, vendorCode);
+            final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+            mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage);
         }
     }
+
+    private static int getHelpCode(int acquireInfo, int vendorCode) {
+        return acquireInfo == FACE_ACQUIRED_VENDOR
+                ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+                : acquireInfo;
+    }
 }
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index bd4d3a0..2ef1430 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -16,6 +16,8 @@
 package android.hardware.face;
 
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 
 /**
  * Communication channel from the FaceService back to FaceAuthenticationManager.
@@ -34,4 +36,6 @@
     void onChallengeGenerated(int sensorId, long challenge);
     void onChallengeInterrupted(int sensorId);
     void onChallengeInterruptFinished(int sensorId);
+    void onAuthenticationFrame(in FaceAuthenticationFrame frame);
+    void onEnrollmentFrame(in FaceEnrollFrame frame);
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 188a2a4..a614ebf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -153,7 +153,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext,
+            return new BiometricTestSession(mContext, sensorId,
                     mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index e01e5ae..f7c1c4b 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -19,7 +19,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.system.ErrnoException;
-import android.system.Int32Ref;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructLinger;
@@ -65,14 +64,11 @@
         public int available() throws IOException {
             FileDescriptor myFd = fd;
             if (myFd == null) throw new IOException("socket closed");
-
-            Int32Ref avail = new Int32Ref(0);
             try {
-                Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
+                return Os.ioctlInt(myFd, OsConstants.FIONREAD);
             } catch (ErrnoException e) {
                 throw e.rethrowAsIOException();
             }
-            return avail.value;
         }
 
         /** {@inheritDoc} */
@@ -134,7 +130,7 @@
         public void write (byte[] b) throws IOException {
             write(b, 0, b.length);
         }
-        
+
         /** {@inheritDoc} */
         @Override
         public void write (byte[] b, int off, int len) throws IOException {
@@ -255,7 +251,7 @@
     /** note timeout presently ignored */
     protected void connect(LocalSocketAddress address, int timeout)
                         throws IOException
-    {        
+    {
         if (fd == null) {
             throw new IOException("socket not created");
         }
@@ -339,7 +335,7 @@
      * @throws IOException if socket has been closed or cannot be created.
      */
     protected OutputStream getOutputStream() throws IOException
-    { 
+    {
         if (fd == null) {
             throw new IOException("socket not created");
         }
diff --git a/core/java/android/net/VpnTransportInfo.java b/core/java/android/net/VpnTransportInfo.java
new file mode 100644
index 0000000..082fa58
--- /dev/null
+++ b/core/java/android/net/VpnTransportInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.util.Objects;
+
+/** @hide */
+public final class VpnTransportInfo implements TransportInfo, Parcelable {
+    private static final SparseArray<String> sTypeToString =
+            MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
+
+    /** Type of this VPN. */
+    @VpnManager.VpnType public final int type;
+
+    public VpnTransportInfo(@VpnManager.VpnType int type) {
+        this.type = type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VpnTransportInfo)) return false;
+
+        VpnTransportInfo that = (VpnTransportInfo) o;
+        return this.type == that.type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type);
+    }
+
+    @Override
+    public String toString() {
+        final String typeString = sTypeToString.get(type, "VPN_TYPE_???");
+        return String.format("VpnTransportInfo{%s}", typeString);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(type);
+    }
+
+    public static final @NonNull Creator<VpnTransportInfo> CREATOR =
+            new Creator<VpnTransportInfo>() {
+        public VpnTransportInfo createFromParcel(Parcel in) {
+            return new VpnTransportInfo(in.readInt());
+        }
+        public VpnTransportInfo[] newArray(int size) {
+            return new VpnTransportInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index fa090f5..1a38338 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -28,8 +28,10 @@
 import android.os.ServiceSpecificException;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -67,8 +69,7 @@
 public class VcnManager {
     @NonNull private static final String TAG = VcnManager.class.getSimpleName();
 
-    @VisibleForTesting
-    public static final Map<
+    private static final Map<
                     VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
             REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();
 
@@ -88,6 +89,18 @@
         mService = requireNonNull(service, "missing service");
     }
 
+    /**
+     * Get all currently registered VcnUnderlyingNetworkPolicyListeners for testing purposes.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    public static Map<VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
+            getAllPolicyListeners() {
+        return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
+    }
+
     // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Sets the VCN configuration for a given subscription group.
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index f2b466d..72a6e16 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -47,6 +47,7 @@
             POWER_COMPONENT_SYSTEM_SERVICES,
             POWER_COMPONENT_SENSORS,
             POWER_COMPONENT_GNSS,
+            POWER_COMPONENT_SCREEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface PowerComponent {
@@ -63,8 +64,9 @@
     public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
     public static final int POWER_COMPONENT_SENSORS = 9;
     public static final int POWER_COMPONENT_GNSS = 10;
+    public static final int POWER_COMPONENT_SCREEN = 13;
 
-    public static final int POWER_COMPONENT_COUNT = 11;
+    public static final int POWER_COMPONENT_COUNT = 14;
 
     public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -85,6 +87,7 @@
             TIME_COMPONENT_MOBILE_RADIO,
             TIME_COMPONENT_SENSORS,
             TIME_COMPONENT_GNSS,
+            TIME_COMPONENT_SCREEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface TimeComponent {
@@ -101,8 +104,9 @@
     public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
     public static final int TIME_COMPONENT_SENSORS = 9;
     public static final int TIME_COMPONENT_GNSS = 10;
+    public static final int TIME_COMPONENT_SCREEN = 13;
 
-    public static final int TIME_COMPONENT_COUNT = 11;
+    public static final int TIME_COMPONENT_COUNT = 14;
 
     public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index af8e8de..305815f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -33,20 +33,33 @@
     private final int mDischargePercentage;
     private final ArrayList<UidBatteryConsumer> mUidBatteryConsumers;
     private final ArrayList<SystemBatteryConsumer> mSystemBatteryConsumers;
+    private final ArrayList<UserBatteryConsumer> mUserBatteryConsumers;
 
     private BatteryUsageStats(@NonNull Builder builder) {
         mConsumedPower = builder.mConsumedPower;
         mDischargePercentage = builder.mDischargePercentage;
+
         int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
         mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
         for (int i = 0; i < uidBatteryConsumerCount; i++) {
-            mUidBatteryConsumers.add(builder.mUidBatteryConsumerBuilders.valueAt(i).build());
+            UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
+                    builder.mUidBatteryConsumerBuilders.valueAt(i);
+            if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
+                mUidBatteryConsumers.add(uidBatteryConsumerBuilder.build());
+            }
         }
+
         int systemBatteryConsumerCount = builder.mSystemBatteryConsumerBuilders.size();
         mSystemBatteryConsumers = new ArrayList<>(systemBatteryConsumerCount);
         for (int i = 0; i < systemBatteryConsumerCount; i++) {
             mSystemBatteryConsumers.add(builder.mSystemBatteryConsumerBuilders.valueAt(i).build());
         }
+
+        int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
+        mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
+        for (int i = 0; i < userBatteryConsumerCount; i++) {
+            mUserBatteryConsumers.add(builder.mUserBatteryConsumerBuilders.valueAt(i).build());
+        }
     }
 
     /**
@@ -75,6 +88,11 @@
         return mSystemBatteryConsumers;
     }
 
+    @NonNull
+    public List<UserBatteryConsumer> getUserBatteryConsumers() {
+        return mUserBatteryConsumers;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -85,6 +103,8 @@
         source.readParcelableList(mUidBatteryConsumers, getClass().getClassLoader());
         mSystemBatteryConsumers = new ArrayList<>();
         source.readParcelableList(mSystemBatteryConsumers, getClass().getClassLoader());
+        mUserBatteryConsumers = new ArrayList<>();
+        source.readParcelableList(mUserBatteryConsumers, getClass().getClassLoader());
         mConsumedPower = source.readDouble();
         mDischargePercentage = source.readInt();
     }
@@ -93,6 +113,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mUidBatteryConsumers, flags);
         dest.writeParcelableList(mSystemBatteryConsumers, flags);
+        dest.writeParcelableList(mUserBatteryConsumers, flags);
         dest.writeDouble(mConsumedPower);
         dest.writeInt(mDischargePercentage);
     }
@@ -120,6 +141,8 @@
                 new SparseArray<>();
         private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
                 new SparseArray<>();
+        private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
+                new SparseArray<>();
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount) {
             mCustomPowerComponentCount = customPowerComponentCount;
@@ -137,7 +160,6 @@
         /**
          * Sets the battery discharge amount since BatteryStats reset as percentage of the full
          * charge.
-         *
          */
         @SuppressLint("PercentageInt") // See b/174188159
         @NonNull
@@ -173,8 +195,8 @@
         }
 
         /**
-         * Creates or returns a exiting UidBatteryConsumer, which represents battery attribution
-         * data for an individual UID.
+         * Creates or returns a exiting SystemBatteryConsumer, which represents battery attribution
+         * data for a specific drain type.
          */
         @NonNull
         public SystemBatteryConsumer.Builder getOrCreateSystemBatteryConsumerBuilder(
@@ -188,6 +210,21 @@
             return builder;
         }
 
+        /**
+         * Creates or returns a exiting UserBatteryConsumer, which represents battery attribution
+         * data for an individual {@link UserHandle}.
+         */
+        @NonNull
+        public UserBatteryConsumer.Builder getOrCreateUserBatteryConsumerBuilder(int userId) {
+            UserBatteryConsumer.Builder builder = mUserBatteryConsumerBuilders.get(userId);
+            if (builder == null) {
+                builder = new UserBatteryConsumer.Builder(mCustomPowerComponentCount,
+                        mCustomTimeComponentCount, userId);
+                mUserBatteryConsumerBuilders.put(userId, builder);
+            }
+            return builder;
+        }
+
         @NonNull
         public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() {
             return mUidBatteryConsumerBuilders;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 48e7389..5b5fe1d 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.util.IntArray;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -53,9 +54,13 @@
     public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 1;
 
     private final int mFlags;
+    @NonNull
+    private final int[] mUserIds;
 
     private BatteryUsageStatsQuery(@NonNull Builder builder) {
         mFlags = builder.mFlags;
+        mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
+                : new int[]{UserHandle.USER_ALL};
     }
 
     @BatteryUsageStatsFlags
@@ -63,13 +68,28 @@
         return mFlags;
     }
 
+    /**
+     * Returns an array of users for which the attribution is requested.  It may
+     * contain {@link UserHandle#USER_ALL} to indicate that the attribution
+     * should be performed for all users. Battery consumed by users <b>not</b> included
+     * in this array will be returned in the aggregated form as {@link UserBatteryConsumer}'s.
+     */
+    @NonNull
+    public int[] getUserIds() {
+        return mUserIds;
+    }
+
     private BatteryUsageStatsQuery(Parcel in) {
         mFlags = in.readInt();
+        mUserIds = new int[in.readInt()];
+        in.readIntArray(mUserIds);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mFlags);
+        dest.writeInt(mUserIds.length);
+        dest.writeIntArray(mUserIds);
     }
 
     @Override
@@ -96,6 +116,7 @@
      */
     public static final class Builder {
         private int mFlags;
+        private IntArray mUserIds;
 
         /**
          * Builds a read-only BatteryUsageStatsQuery object.
@@ -105,6 +126,18 @@
         }
 
         /**
+         * Add a user whose battery stats should be included in the battery usage stats.
+         * {@link UserHandle#USER_ALL} will be used by default if no users are added explicitly.
+         */
+        public Builder addUser(@NonNull UserHandle userHandle) {
+            if (mUserIds == null) {
+                mUserIds = new IntArray(1);
+            }
+            mUserIds.add(userHandle.getIdentifier());
+            return this;
+        }
+
+        /**
          * Sets flags to modify the behavior of {@link BatteryStatsManager#getBatteryUsageStats}.
          */
         public Builder setFlags(@BatteryUsageStatsFlags int flags) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 9584bc7..6a76da2 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2557,6 +2557,14 @@
     public static native long getZramFreeKb();
 
     /**
+     * Return total memory size in kilobytes for exported DMA-BUFs or -1 if
+     * the DMA-BUF sysfs stats at /sys/kernel/dmabuf/buffers could not be read.
+     *
+     * @hide
+     */
+    public static native long getDmabufTotalExportedKb();
+
+    /**
      * Return memory size in kilobytes allocated for ION heaps or -1 if
      * /sys/kernel/ion/total_heaps_kb could not be read.
      *
@@ -2565,6 +2573,14 @@
     public static native long getIonHeapsSizeKb();
 
     /**
+     * Return memory size in kilobytes allocated for DMA-BUF heap pools or -1 if
+     * /sys/kernel/dma_heap/total_pools_kb could not be read.
+     *
+     * @hide
+     */
+    public static native long getDmabufHeapPoolsSizeKb();
+
+    /**
      * Return memory size in kilobytes allocated for ION pools or -1 if
      * /sys/kernel/ion/total_pools_kb could not be read.
      *
@@ -2573,13 +2589,13 @@
     public static native long getIonPoolsSizeKb();
 
     /**
-     * Return ION memory mapped by processes in kB.
+     * Return DMA-BUF memory mapped by processes in kB.
      * Notes:
      *  * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
      *
      * @hide
      */
-    public static native long getIonMappedSizeKb();
+    public static native long getDmabufMappedSizeKb();
 
     /**
      * Return memory size in kilobytes used by GPU.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 518e29d..124c0b0 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.AppGlobals;
@@ -836,6 +837,21 @@
     public static String DIRECTORY_AUDIOBOOKS = "Audiobooks";
 
     /**
+     * Standard directory in which to place any audio files which are
+     * recordings.
+     */
+    @NonNull
+    // The better way is that expose a static method getRecordingDirectories.
+    // But since it's an existing API surface and developers already
+    // used to DIRECTORY_* constants, we should keep using this pattern
+    // for consistency. We use SuppressLint here to avoid exposing a final
+    // field. A final field will prevent us from ever changing the value of
+    // DIRECTORY_RECORDINGS. Not that it's likely that we will ever need to
+    // change it, but it's better to have such option.
+    @SuppressLint({"MutableBareField", "AllUpper"})
+    public static String DIRECTORY_RECORDINGS = "Recordings";
+
+    /**
      * List of standard storage directories.
      * <p>
      * Each of its values have its own constant:
@@ -851,6 +867,7 @@
      *   <li>{@link #DIRECTORY_DCIM}
      *   <li>{@link #DIRECTORY_DOCUMENTS}
      *   <li>{@link #DIRECTORY_AUDIOBOOKS}
+     *   <li>{@link #DIRECTORY_RECORDINGS}
      * </ul>
      * @hide
      */
@@ -866,6 +883,7 @@
             DIRECTORY_DCIM,
             DIRECTORY_DOCUMENTS,
             DIRECTORY_AUDIOBOOKS,
+            DIRECTORY_RECORDINGS,
     };
 
     /**
@@ -891,6 +909,7 @@
     /** {@hide} */ public static final int HAS_DCIM = 1 << 8;
     /** {@hide} */ public static final int HAS_DOCUMENTS = 1 << 9;
     /** {@hide} */ public static final int HAS_AUDIOBOOKS = 1 << 10;
+    /** {@hide} */ public static final int HAS_RECORDINGS = 1 << 11;
 
     /** {@hide} */ public static final int HAS_ANDROID = 1 << 16;
     /** {@hide} */ public static final int HAS_OTHER = 1 << 17;
@@ -921,6 +940,7 @@
                 else if (DIRECTORY_DCIM.equals(name)) res |= HAS_DCIM;
                 else if (DIRECTORY_DOCUMENTS.equals(name)) res |= HAS_DOCUMENTS;
                 else if (DIRECTORY_AUDIOBOOKS.equals(name)) res |= HAS_AUDIOBOOKS;
+                else if (DIRECTORY_RECORDINGS.equals(name)) res |= HAS_RECORDINGS;
                 else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID;
                 else res |= HAS_OTHER;
             }
@@ -1339,8 +1359,17 @@
         }
 
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
-                uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
+        final String opPackageName = context.getOpPackageName();
+
+        if (appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, uid,
+                opPackageName) == AppOpsManager.MODE_ALLOWED) {
+            return true;
+        }
+
+        // Legacy external storage access is granted to instrumentations invoked with
+        // "--no-isolated-storage" flag.
+        return appOps.noteOpNoThrow(AppOpsManager.OP_NO_ISOLATED_STORAGE, uid,
+                opPackageName) == AppOpsManager.MODE_ALLOWED;
     }
 
     private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 39e3e14..8068c87 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1442,7 +1442,7 @@
      *
      * @hide
      */
-    public static boolean hasFileLocks(int pid) throws IOException {
+    public static boolean hasFileLocks(int pid) throws Exception {
         BufferedReader br = null;
 
         try {
@@ -1454,8 +1454,13 @@
 
                 for (int i = 0; i < 5 && st.hasMoreTokens(); i++) {
                     String str = st.nextToken();
-                    if (i == 4 && Integer.parseInt(str) == pid) {
-                        return true;
+                    try {
+                        if (i == 4 && Integer.parseInt(str) == pid) {
+                            return true;
+                        }
+                    } catch (NumberFormatException nfe) {
+                        throw new Exception("Exception parsing /proc/locks at \" "
+                                + line +  " \", token #" + i);
                     }
                 }
             }
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 3ffaa9e..a828077 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -96,14 +96,16 @@
         private final int mUid;
         private String mPackageWithHighestDrain;
         private boolean mSystemComponent;
+        private boolean mExcludeFromBatteryUsageStats;
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount,
-                BatteryStats.Uid batteryStatsUid) {
+                @NonNull BatteryStats.Uid batteryStatsUid) {
             super(customPowerComponentCount, customTimeComponentCount);
             mBatteryStatsUid = batteryStatsUid;
             mUid = batteryStatsUid.getUid();
         }
 
+        @NonNull
         public BatteryStats.Uid getBatteryStatsUid() {
             return mBatteryStatsUid;
         }
@@ -113,14 +115,6 @@
         }
 
         /**
-         * Creates a read-only object out of the Builder values.
-         */
-        @NonNull
-        public UidBatteryConsumer build() {
-            return new UidBatteryConsumer(this);
-        }
-
-        /**
          * Sets the name of the package owned by this UID that consumed the highest amount
          * of power since BatteryStats reset.
          */
@@ -131,12 +125,27 @@
         }
 
         /**
-         * Marks the UidBatteryConsumer as part of the system. For example,
-         * the UidBatteryConsumer with the UID {@link Process#BLUETOOTH_UID} is considered
-         * as a system component.
+         * Marks the UidBatteryConsumer for exclusion from the result set.
          */
-        public void setSystemComponent(boolean systemComponent) {
-            mSystemComponent = systemComponent;
+        public Builder excludeFromBatteryUsageStats() {
+            mExcludeFromBatteryUsageStats = true;
+            return this;
+        }
+
+        /**
+         * Returns true if this UidBatteryConsumer must be excluded from the
+         * BatteryUsageStats.
+         */
+        public boolean isExcludedFromBatteryUsageStats() {
+            return mExcludeFromBatteryUsageStats;
+        }
+
+        /**
+         * Creates a read-only object out of the Builder values.
+         */
+        @NonNull
+        public UidBatteryConsumer build() {
+            return new UidBatteryConsumer(this);
         }
     }
 }
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
new file mode 100644
index 0000000..94e567f
--- /dev/null
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains power consumption data attributed to a {@link UserHandle}.
+ *
+ * {@hide}
+ */
+public class UserBatteryConsumer extends BatteryConsumer implements Parcelable {
+    private final int mUserId;
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    private UserBatteryConsumer(@NonNull UserBatteryConsumer.Builder builder) {
+        super(builder.mPowerComponentsBuilder.build());
+        mUserId = builder.mUserId;
+    }
+
+    private UserBatteryConsumer(Parcel in) {
+        super(new PowerComponents(in));
+        mUserId = in.readInt();
+    }
+
+    /**
+     * Writes the contents into a Parcel.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mUserId);
+    }
+
+    public static final Creator<UserBatteryConsumer> CREATOR =
+            new Creator<UserBatteryConsumer>() {
+                @Override
+                public UserBatteryConsumer createFromParcel(Parcel in) {
+                    return new UserBatteryConsumer(in);
+                }
+
+                @Override
+                public UserBatteryConsumer[] newArray(int size) {
+                    return new UserBatteryConsumer[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Builder for UserBatteryConsumer.
+     */
+    public static final class Builder extends BaseBuilder<Builder> {
+        private final int mUserId;
+        private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
+
+        Builder(int customPowerComponentCount, int customTimeComponentCount, int userId) {
+            super(customPowerComponentCount, customTimeComponentCount);
+            mUserId = userId;
+        }
+
+        /**
+         * Add a UidBatteryConsumer to this UserBatteryConsumer.
+         * <p>
+         * Calculated power and duration components of the added UID battery consumers
+         * are aggregated at the time the UserBatteryConsumer is built by the {@link #build()}
+         * method.
+         * </p>
+         */
+        public void addUidBatteryConsumer(UidBatteryConsumer.Builder uidBatteryConsumerBuilder) {
+            if (mUidBatteryConsumers == null) {
+                mUidBatteryConsumers = new ArrayList<>();
+            }
+            mUidBatteryConsumers.add(uidBatteryConsumerBuilder);
+        }
+
+        /**
+         * Creates a read-only object out of the Builder values.
+         */
+        @NonNull
+        public UserBatteryConsumer build() {
+            if (mUidBatteryConsumers != null) {
+                for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
+                    UidBatteryConsumer.Builder uidBatteryConsumer = mUidBatteryConsumers.get(i);
+                    mPowerComponentsBuilder.addPowerAndDuration(
+                            uidBatteryConsumer.mPowerComponentsBuilder);
+                }
+            }
+            return new UserBatteryConsumer(this);
+        }
+    }
+}
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index faa90d9..86b20c4 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -350,6 +350,9 @@
             return cachedTypeface;
         }
 
+        Log.w(TAG, "Platform version of downloadable fonts is deprecated. Please use"
+                + " androidx version instead.");
+
         synchronized (sLock) {
             // It is possible that Font is loaded during the thread sleep time
             // re-check the cache to avoid re-loading the font
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 990b7bd..dad932c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -123,14 +123,6 @@
     boolean outOfMemory(IWindow window);
 
     /**
-     * Give the window manager a hint of the part of the window that is
-     * completely transparent, allowing it to work with the surface flinger
-     * to optimize compositing of this part of the window.
-     */
-    @UnsupportedAppUsage
-    oneway void setTransparentRegion(IWindow window, in Region region);
-
-    /**
      * Tell the window manager about the content and visible insets of the
      * given window, which can be used to adjust the <var>outContentInsets</var>
      * and <var>outVisibleInsets</var> values returned by
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index cbc0479..20f0598 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -32,7 +32,6 @@
 
     private static native long nativeCreate();
     private static native void nativeDestroy(long ptr);
-    private static native void nativeKill(long ptr);
 
     /** Create a new connection with the surface flinger. */
     @UnsupportedAppUsage
@@ -44,22 +43,22 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mNativeClient != 0) {
-                nativeDestroy(mNativeClient);
-            }
+            kill();
         } finally {
             super.finalize();
         }
     }
 
     /**
-     * Forcibly detach native resources associated with this object.
-     * Unlike destroy(), after this call any surfaces that were created
-     * from the session will no longer work.
+     * Remove the reference to the native Session object. The native object may still exist if
+     * there are other references to it, but it cannot be accessed from this Java object anymore.
      */
     @UnsupportedAppUsage
     public void kill() {
-        nativeKill(mNativeClient);
+        if (mNativeClient != 0) {
+            nativeDestroy(mNativeClient);
+            mNativeClient = 0;
+        }
     }
 }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e1ccc51..1273b49 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -740,6 +740,7 @@
  * @attr ref android.R.styleable#View_alpha
  * @attr ref android.R.styleable#View_background
  * @attr ref android.R.styleable#View_clickable
+ * @attr ref android.R.styleable#View_clipToOutline
  * @attr ref android.R.styleable#View_contentDescription
  * @attr ref android.R.styleable#View_drawingCacheQuality
  * @attr ref android.R.styleable#View_duplicateParentState
@@ -5968,6 +5969,9 @@
                 case R.styleable.View_scrollCaptureHint:
                     setScrollCaptureHint((a.getInt(attr, SCROLL_CAPTURE_HINT_AUTO)));
                     break;
+                case R.styleable.View_clipToOutline:
+                    setClipToOutline(a.getBoolean(attr, false));
+                    break;
             }
         }
 
@@ -17921,6 +17925,8 @@
      *
      * @see #setOutlineProvider(ViewOutlineProvider)
      * @see #getClipToOutline()
+     *
+     * @attr ref android.R.styleable#View_clipToOutline
      */
     @RemotableViewMethod
     public void setClipToOutline(boolean clipToOutline) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index acc77b5..755ae31 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1913,25 +1913,18 @@
     private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
         if (mBoundsLayer != null) {
             setBoundsLayerCrop(t);
-            t.deferTransactionUntil(mBoundsLayer, getSurfaceControl(),
-                mSurface.getNextFrameNumber());
             return true;
         }
         return false;
     }
 
-    private void prepareSurfaces(boolean sizeChanged) {
+    private void prepareSurfaces() {
         final SurfaceControl.Transaction t = mTransaction;
         final SurfaceControl sc = getSurfaceControl();
         if (!sc.isValid()) return;
 
-        boolean applyTransaction = updateBoundsLayer(t);
-        if (sizeChanged) {
-            applyTransaction = true;
-            t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y);
-        }
-        if (applyTransaction) {
-            t.apply();
+        if (updateBoundsLayer(t)) {
+              mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
         }
     }
 
@@ -3036,7 +3029,7 @@
             // stopping, but on the client side it doesn't get stopped since it's restarted quick
             // enough. WMS doesn't want to keep around old children since they will leak when the
             // client creates new children.
-            prepareSurfaces(surfaceSizeChanged);
+            prepareSurfaces();
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -3064,11 +3057,14 @@
                 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                     mPreviousTransparentRegion.set(mTransparentRegion);
                     mFullRedrawNeeded = true;
-                    // reconfigure window manager
-                    try {
-                        mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
-                    } catch (RemoteException e) {
-                    }
+                    // TODO: Ideally we would do this in prepareSurfaces,
+                    // but prepareSurfaces is currently working under
+                    // the assumption that we paused the render thread
+                    // via the WM relayout code path. We probably eventually
+                    // want to synchronize transparent region hint changes
+                    // with draws.
+                    mTransaction.setTransparentRegionHint(getSurfaceControl(),
+                        mTransparentRegion).apply();
                 }
             }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 3aedda1..39d3c01 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -314,10 +314,6 @@
     }
 
     @Override
-    public void setTransparentRegion(android.view.IWindow window, android.graphics.Region region) {
-    }
-
-    @Override
     public void setInsets(android.view.IWindow window, int touchableInsets,
             android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets,
             android.graphics.Region touchableRegion) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 794181e..decbf8c 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -200,6 +200,34 @@
             "android.view.autofill.extra.AUTHENTICATION_RESULT";
 
     /**
+     * Intent extra: The optional boolean extra field provided by the
+     * {@link android.service.autofill.AutofillService} accompanying the {@link
+     * android.service.autofill.Dataset} result of an authentication operation.
+     *
+     * <p> Before {@link android.os.Build.VERSION_CODES#R}, if the authentication result is a
+     * {@link android.service.autofill.Dataset}, it'll be used to autofill the fields, and also
+     * replace the existing dataset in the cached {@link android.service.autofill.FillResponse}.
+     * That means if the user clears the field values, the autofill suggestion will show up again
+     * with the new authenticated Dataset.
+     *
+     * <p> In {@link android.os.Build.VERSION_CODES#R}, we added an exception to this behavior
+     * that if the Dataset being authenticated is a pinned dataset (see
+     * {@link android.service.autofill.InlinePresentation#isPinned()}), the old Dataset will not be
+     * replaced.
+     *
+     * <p> In {@link android.os.Build.VERSION_CODES#S}, we added this boolean extra field to
+     * allow the {@link android.service.autofill.AutofillService} to explicitly specify whether
+     * the returned authenticated Dataset is ephemeral. An ephemeral Dataset will be used to
+     * autofill once and then thrown away. Therefore, when the boolean extra is set to true, the
+     * returned Dataset will not replace the old dataset from the existing
+     * {@link android.service.autofill.FillResponse}. When it's set to false, it will. When it's not
+     * set, the old dataset will be replaced, unless it is a pinned inline suggestion, which is
+     * consistent with the behavior in {@link android.os.Build.VERSION_CODES#R}.
+     */
+    public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET =
+            "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
+
+    /**
      * Intent extra: The optional extras provided by the
      * {@link android.service.autofill.AutofillService}.
      *
@@ -1755,6 +1783,11 @@
             if (newClientState != null) {
                 responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
             }
+            if (data.getExtras().containsKey(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+                responseData.putBoolean(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                        data.getBooleanExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                                false));
+            }
             try {
                 mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
                         mContext.getUserId());
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 90c8e17..7b2bb73 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -115,7 +115,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -1083,19 +1082,6 @@
         }
     }
 
-    private static class ImeThreadFactory implements ThreadFactory {
-        private final String mThreadName;
-
-        ImeThreadFactory(String name) {
-            mThreadName = name;
-        }
-
-        @Override
-        public Thread newThread(Runnable r) {
-            return new Thread(r, mThreadName);
-        }
-    }
-
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index c10ffbe..61ff36c 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.ColorInt;
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -30,6 +31,9 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This class performs the graphical effect used at the edges of scrollable widgets
  * when the user scrolls beyond the content bounds in 2D space.
@@ -55,6 +59,24 @@
      */
     public static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_ATOP;
 
+    /**
+     * Use a color edge glow for the edge effect. From XML, use
+     * <code>android:edgeEffectType="glow"</code>.
+     */
+    public static final int TYPE_GLOW = 0;
+
+    /**
+     * Use a stretch for the edge effect. From XML, use
+     * <code>android:edgeEffectType="stretch"</code>.
+     */
+    public static final int TYPE_STRETCH = 1;
+
+    /** @hide */
+    @IntDef({TYPE_GLOW, TYPE_STRETCH})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EdgeEffectType {
+    }
+
     @SuppressWarnings("UnusedDeclaration")
     private static final String TAG = "EdgeEffect";
 
@@ -121,6 +143,7 @@
     private float mBaseGlowScale;
     private float mDisplacement = 0.5f;
     private float mTargetDisplacement = 0.5f;
+    private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW;
 
     /**
      * Construct a new EdgeEffect with a theme appropriate for the provided context.
@@ -132,6 +155,8 @@
                 com.android.internal.R.styleable.EdgeEffect);
         final int themeColor = a.getColor(
                 com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
+        mEdgeEffectType = a.getInt(
+                com.android.internal.R.styleable.EdgeEffect_edgeEffectType, TYPE_GLOW);
         a.recycle();
         mPaint.setColor((themeColor & 0xffffff) | 0x33000000);
         mPaint.setStyle(Paint.Style.FILL);
@@ -309,6 +334,17 @@
     }
 
     /**
+     * Sets the edge effect type to use. The default without a theme attribute set is
+     * {@link EdgeEffect#TYPE_GLOW}.
+     *
+     * @param type The edge effect type to use.
+     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
+     */
+    public void setType(@EdgeEffectType int type) {
+        mEdgeEffectType = type;
+    }
+
+    /**
      * Set or clear the blend mode. A blend mode defines how source pixels
      * (generated by a drawing command) are composited with the destination pixels
      * (content of the render target).
@@ -333,6 +369,15 @@
         return mPaint.getColor();
     }
 
+    /**
+     * Return the edge effect type to use.
+     *
+     * @return The edge effect type to use.
+     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
+     */
+    public @EdgeEffectType int getType() {
+        return mEdgeEffectType;
+    }
 
     /**
      * Returns the blend mode. A blend mode defines how source pixels
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index dfef7ca..6cb4b81 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -47,6 +47,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.Outline;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -79,6 +80,7 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewManager;
+import android.view.ViewOutlineProvider;
 import android.view.ViewParent;
 import android.view.ViewStub;
 import android.widget.AdapterView.OnItemClickListener;
@@ -194,6 +196,7 @@
     private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25;
     private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26;
     private static final int SET_RADIO_GROUP_CHECKED = 27;
+    private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -2642,6 +2645,88 @@
         }
     }
 
+    private static class SetViewOutlinePreferredRadiusAction extends Action {
+
+        private final boolean mIsDimen;
+        private final int mValue;
+
+        SetViewOutlinePreferredRadiusAction(@IdRes int viewId, @DimenRes int dimenResId) {
+            this.viewId = viewId;
+            this.mIsDimen = true;
+            this.mValue = dimenResId;
+        }
+
+        SetViewOutlinePreferredRadiusAction(
+                @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
+            this.viewId = viewId;
+            this.mIsDimen = false;
+            this.mValue = TypedValue.createComplexDimension(radius, units);
+
+        }
+
+        SetViewOutlinePreferredRadiusAction(Parcel in) {
+            viewId = in.readInt();
+            mIsDimen = in.readBoolean();
+            mValue = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(viewId);
+            dest.writeBoolean(mIsDimen);
+            dest.writeInt(mValue);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+                throws ActionException {
+            final View target = root.findViewById(viewId);
+            if (target == null) return;
+
+            float radius;
+            if (mIsDimen) {
+                radius = mValue == 0 ? 0 : target.getResources().getDimension(mValue);
+            } else {
+                radius = TypedValue.complexToDimensionPixelSize(mValue,
+                        target.getResources().getDisplayMetrics());
+            }
+            target.setOutlineProvider(new RemoteViewOutlineProvider(radius));
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_VIEW_OUTLINE_RADIUS_TAG;
+        }
+    }
+
+    /**
+     * OutlineProvider for a view with a radius set by
+     * {@link #setViewOutlinePreferredRadius(int, float, int)}.
+     */
+    public static final class RemoteViewOutlineProvider extends ViewOutlineProvider {
+
+        private final float mRadius;
+
+        public RemoteViewOutlineProvider(float radius) {
+            mRadius = radius;
+        }
+
+        /** Returns the corner radius used when providing the view outline. */
+        public float getRadius() {
+            return mRadius;
+        }
+
+        @Override
+        public void getOutline(@NonNull View view, @NonNull Outline outline) {
+            outline.setRoundRect(
+                    0 /*left*/,
+                    0 /* top */,
+                    view.getWidth() /* right */,
+                    view.getHeight() /* bottom */,
+                    mRadius);
+        }
+    }
+
     /**
      * Create a new RemoteViews object that will display the views contained
      * in the specified layout file.
@@ -2860,6 +2945,8 @@
                 return new SetCompoundButtonCheckedAction(parcel);
             case SET_RADIO_GROUP_CHECKED:
                 return new SetRadioGroupCheckedAction(parcel);
+            case SET_VIEW_OUTLINE_RADIUS_TAG:
+                return new SetViewOutlinePreferredRadiusAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -3595,6 +3682,28 @@
     }
 
     /**
+     * Sets an OutlineProvider on the view whose corner radius is a dimension calculated using
+     * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}. This outline may change shape
+     * during system transitions.
+     *
+     * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
+     * Setting margins in pixels will behave poorly when the RemoteViews object is used on a
+     * display with a different density.
+     */
+    public void setViewOutlinePreferredRadius(
+            @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
+        addAction(new SetViewOutlinePreferredRadiusAction(viewId, radius, units));
+    }
+
+    /**
+     * Sets an OutlineProvider on the view whose corner radius is a dimension resource with
+     * {@code resId}. This outline may change shape during system transitions.
+     */
+    public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) {
+        addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId));
+    }
+
+    /**
      * Call a method taking one boolean on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view on which to call the method.
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index b484dfa..2904a8c 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -172,6 +172,22 @@
     }
 
     /**
+     * Update the LayoutParameters of the currently showing toast view. This is used for layout
+     * updates based on orientation changes.
+     */
+    public void updateLayoutParams(int xOffset, int yOffset, float horizontalMargin,
+            float verticalMargin, int gravity) {
+        checkState(mView != null, "Toast must be showing to update its layout parameters.");
+        Configuration config = mResources.getConfiguration();
+        mParams.gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
+        mParams.x = xOffset;
+        mParams.y = yOffset;
+        mParams.horizontalMargin = horizontalMargin;
+        mParams.verticalMargin = verticalMargin;
+        addToastView();
+    }
+
+    /**
      * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
      * packageName} is a cross-user package.
      *
@@ -221,18 +237,7 @@
 
         adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                 horizontalMargin, verticalMargin, removeWindowAnimations);
-        if (mView.getParent() != null) {
-            mWindowManager.removeView(mView);
-        }
-        try {
-            mWindowManager.addView(mView, mParams);
-        } catch (WindowManager.BadTokenException e) {
-            // Since the notification manager service cancels the token right after it notifies us
-            // to cancel the toast there is an inherent race and we may attempt to add a window
-            // after the token has been invalidated. Let us hedge against that.
-            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
-            return;
-        }
+        addToastView();
         trySendAccessibilityEvent(mView, mPackageName);
         if (callback != null) {
             try {
@@ -288,4 +293,19 @@
         view.dispatchPopulateAccessibilityEvent(event);
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
+
+    private void addToastView() {
+        if (mView.getParent() != null) {
+            mWindowManager.removeView(mView);
+        }
+        try {
+            mWindowManager.addView(mView, mParams);
+        } catch (WindowManager.BadTokenException e) {
+            // Since the notification manager service cancels the token right after it notifies us
+            // to cancel the toast there is an inherent race and we may attempt to add a window
+            // after the token has been invalidated. Let us hedge against that.
+            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+            return;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
similarity index 99%
rename from core/java/com/android/internal/BrightnessSynchronizer.java
rename to core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9049ca5..fae5862 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal;
+package com.android.internal.display;
 
 
 import android.content.ContentResolver;
diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/core/java/com/android/internal/display/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 8fe17fb..1313090 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -43,8 +43,7 @@
      */
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = mPowerEstimator.calculatePower(durationMs);
@@ -84,7 +83,7 @@
 
     private double getMeasuredOrEstimatedPower(long measuredEnergyUJ, long durationMs) {
         if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
-            return mAhToUJ(measuredEnergyUJ);
+            return uJtoMah(measuredEnergyUJ);
         } else {
             return mPowerEstimator.calculatePower(durationMs);
         }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 1f7a7aa..0da2998 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -104,6 +104,7 @@
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.power.MeasuredEnergyStats.StandardEnergyBucket;
 import com.android.internal.util.ArrayUtils;
@@ -7172,6 +7173,20 @@
                 .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
     }
 
+    /**
+     * Returns the energy in microjoules that the given custom energy bucket consumed.
+     * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+     *
+     * @param customEnergyBucket custom energy bucket of interest
+     * @return energy (in microjoules) used by this uid for this energy bucket
+     */
+    public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) {
+        if (mGlobalMeasuredEnergyStats == null) {
+            return ENERGY_DATA_UNAVAILABLE;
+        }
+        return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket);
+    }
+
     @Override public long getStartClockTime() {
         final long currentTimeMs = System.currentTimeMillis();
         if ((currentTimeMs > MILLISECONDS_IN_YEAR
@@ -7940,6 +7955,13 @@
                     .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
         }
 
+        /** Adds the given energy to the given custom energy bucket for this uid. */
+        private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
+                boolean accumulate) {
+            getOrCreateMeasuredEnergyStatsLocked()
+                    .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
+        }
+
         /**
          * Returns the energy used by this uid for a standard energy bucket of interest.
          * @param bucket standard energy bucket of interest
@@ -7957,6 +7979,22 @@
         }
 
         /**
+         * Returns the energy used by this uid for a custom energy bucket of interest.
+         * @param customEnergyBucket custom energy bucket of interest
+         * @return energy (in microjoules) used by this uid for this energy bucket
+         */
+        public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) {
+            if (mBsi.mGlobalMeasuredEnergyStats == null
+                    || !mBsi.mGlobalMeasuredEnergyStats.isValidCustomBucket(customEnergyBucket)) {
+                return ENERGY_DATA_UNAVAILABLE;
+            }
+            if (mUidMeasuredEnergyStats == null) {
+                return 0L; // It is supported, but was never filled, so it must be 0
+            }
+            return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket);
+        }
+
+        /**
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
          *
@@ -10842,6 +10880,10 @@
         mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
     }
 
+    public SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes() {
+        return mSystemServerCpuThreadReader.readAbsolute();
+    }
+
     public void setCallback(BatteryCallback cb) {
         mCallback = cb;
     }
@@ -12460,6 +12502,42 @@
     }
 
     /**
+     * Accumulate Custom energy bucket energy, globally and for each app.
+     *
+     * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
+     * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
+     *                    Data inside uidEnergies will not be modified (treated immutable).
+     */
+    public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
+            long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
+                    + customEnergyBucket
+                    + " with total energy " + totalEnergyUJ
+                    + " and uid energies " + String.valueOf(uidEnergies));
+        }
+        if (mGlobalMeasuredEnergyStats == null) return;
+        if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
+
+        mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
+
+        if (uidEnergies == null) return;
+        final int numUids = uidEnergies.size();
+        for (int i = 0; i < numUids; i++) {
+            final int uidInt = mapUid(uidEnergies.keyAt(i));
+            final long uidEnergyUJ = uidEnergies.valueAt(i);
+            if (uidEnergyUJ == 0) continue;
+            // TODO: Worry about uids not in BSI currently, including uninstalled uids 'coming back'
+            //  Specifically: What if the uid had been removed? We'll re-create it now.
+            //  And if we instead use getAvailableUidStatsLocked() and chec for null, then we might
+            //  not create a Uid even when we should be (say, the app's first event, somehow, was to
+            //  use GPU). I guess that CPU/kernel data might already have this problem?
+            final Uid uidObj = getUidStatsLocked(uidInt);
+            uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+        }
+    }
+
+    /**
      * Read and record Rail Energy data.
      */
     public void updateRailStatsLocked() {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index e76e34f..094724c 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -24,7 +24,6 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.SparseArray;
 
 import java.util.ArrayList;
@@ -89,26 +88,27 @@
         final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
                 false /* collectBatteryBroadcast */);
         batteryStatsHelper.create((Bundle) null);
-        final UserManager userManager = mContext.getSystemService(UserManager.class);
-        final List<UserHandle> asUsers = userManager.getUserProfiles();
-        final int n = asUsers.size();
-        SparseArray<UserHandle> users = new SparseArray<>(n);
-        for (int i = 0; i < n; ++i) {
-            UserHandle userHandle = asUsers.get(i);
-            users.put(userHandle.getIdentifier(), userHandle);
+        final List<UserHandle> users = new ArrayList<>();
+        for (int i = 0; i < queries.size(); i++) {
+            BatteryUsageStatsQuery query = queries.get(i);
+            for (int userId : query.getUserIds()) {
+                UserHandle userHandle = UserHandle.of(userId);
+                if (!users.contains(userHandle)) {
+                    users.add(userHandle);
+                }
+            }
         }
-
         batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
 
         ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
         for (int i = 0; i < queries.size(); i++) {
-            results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper, users));
+            results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper));
         }
         return results;
     }
 
     private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
-            BatteryStatsHelper batteryStatsHelper, SparseArray<UserHandle> users) {
+            BatteryStatsHelper batteryStatsHelper) {
         // TODO(b/174186358): read extra power component number from configuration
         final int customPowerComponentCount = 0;
         final int customTimeComponentCount = 0;
@@ -128,8 +128,8 @@
 
         final List<PowerCalculator> powerCalculators = getPowerCalculators();
         for (PowerCalculator powerCalculator : powerCalculators) {
-            powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs, query,
-                    users);
+            powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
+                    query);
         }
 
         return batteryUsageStatsBuilder.build();
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 4c3b950..7d42de4 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -50,8 +50,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) {
             return;
         }
@@ -68,7 +67,7 @@
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             calculateApp(app, total);
             if (app.getUid() == Process.BLUETOOTH_UID) {
-                app.setSystemComponent(true);
+                app.excludeFromBatteryUsageStats();
                 systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
             }
         }
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 11c8761..45d8128 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -17,81 +17,166 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.List;
 
 public class CpuPowerCalculator extends PowerCalculator {
     private static final String TAG = "CpuPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
-    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
-    private final PowerProfile mProfile;
+    private final int mNumCpuClusters;
+
+    // Time-in-state based CPU power estimation model computes the estimated power
+    // by adding up three components:
+    //   - CPU Active power:    the constant amount of charge consumed by the CPU when it is on
+    //   - Per Cluster power:   the additional amount of charge consumed by a CPU cluster
+    //                          when it is running
+    //   - Per frequency power: the additional amount of charge caused by dynamic frequency scaling
+
+    private final UsageBasedPowerEstimator mCpuActivePowerEstimator;
+    // One estimator per cluster
+    private final UsageBasedPowerEstimator[] mPerClusterPowerEstimators;
+    // Multiple estimators per cluster: one per available scaling frequency. Note that different
+    // clusters have different sets of frequencies and corresponding power consumption averages.
+    private final UsageBasedPowerEstimator[][] mPerCpuFreqPowerEstimators;
+
+    private static class Result {
+        public long durationMs;
+        public double powerMah;
+        public long durationFgMs;
+        public String packageWithHighestDrain;
+    }
 
     public CpuPowerCalculator(PowerProfile profile) {
-        mProfile = profile;
+        mNumCpuClusters = profile.getNumCpuClusters();
+
+        mCpuActivePowerEstimator = new UsageBasedPowerEstimator(
+                profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE));
+
+        mPerClusterPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters];
+        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+            mPerClusterPowerEstimators[cluster] = new UsageBasedPowerEstimator(
+                    profile.getAveragePowerForCpuCluster(cluster));
+        }
+
+        mPerCpuFreqPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters][];
+        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+            final int speedsForCluster = profile.getNumSpeedStepsInCpuCluster(cluster);
+            mPerCpuFreqPowerEstimators[cluster] = new UsageBasedPowerEstimator[speedsForCluster];
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                mPerCpuFreqPowerEstimators[cluster][speed] =
+                        new UsageBasedPowerEstimator(
+                                profile.getAveragePowerForCpuCore(cluster, speed));
+            }
+        }
     }
 
     @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        final int statsType = BatteryStats.STATS_SINCE_CHARGED;
+        Result result = new Result();
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            calculateApp(app, app.getBatteryStatsUid(), result);
+        }
+    }
 
-        long cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
-        final int numClusters = mProfile.getNumCpuClusters();
+    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, Result result) {
+        calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, result);
 
-        double cpuPowerMaUs = 0;
-        for (int cluster = 0; cluster < numClusters; cluster++) {
-            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
-            for (int speed = 0; speed < speedsForCluster; speed++) {
-                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
-                final double cpuSpeedStepPower = timeUs *
-                        mProfile.getAveragePowerForCpuCore(cluster, speed);
-                if (DEBUG) {
-                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
-                            + speed + " timeUs=" + timeUs + " power="
-                            + formatCharge(cpuSpeedStepPower / MICROSEC_IN_HR));
-                }
-                cpuPowerMaUs += cpuSpeedStepPower;
+        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, result.powerMah)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, result.durationMs)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND,
+                        result.durationFgMs)
+                .setPackageWithHighestDrain(result.packageWithHighestDrain);
+    }
+
+    @Override
+    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
+        Result result = new Result();
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                calculateApp(app, app.uidObj, statsType, result);
             }
         }
-        cpuPowerMaUs += u.getCpuActiveTime() * 1000 * mProfile.getAveragePower(
-                PowerProfile.POWER_CPU_ACTIVE);
+    }
+
+    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) {
+        calculatePowerAndDuration(u, statsType, result);
+
+        app.cpuPowerMah = result.powerMah;
+        app.cpuTimeMs = result.durationMs;
+        app.cpuFgTimeMs = result.durationFgMs;
+        app.packageWithHighestDrain = result.packageWithHighestDrain;
+    }
+
+    private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, Result result) {
+        long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
+
+        // Constant battery drain when CPU is active
+        double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime());
+
+        // Additional per-cluster battery drain
         long[] cpuClusterTimes = u.getCpuClusterTimes();
         if (cpuClusterTimes != null) {
-            if (cpuClusterTimes.length == numClusters) {
-                for (int i = 0; i < numClusters; i++) {
-                    double power =
-                            cpuClusterTimes[i] * 1000 * mProfile.getAveragePowerForCpuCluster(i);
-                    cpuPowerMaUs += power;
+            if (cpuClusterTimes.length == mNumCpuClusters) {
+                for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+                    double power = mPerClusterPowerEstimators[cluster]
+                            .calculatePower(cpuClusterTimes[cluster]);
+                    powerMah += power;
                     if (DEBUG) {
-                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
-                                + cpuClusterTimes[i] + " power="
-                                + formatCharge(power / MICROSEC_IN_HR));
+                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
+                                + " clusterTimeMs=" + cpuClusterTimes[cluster]
+                                + " power=" + formatCharge(power));
                     }
                 }
             } else {
                 Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
-                        + numClusters + " actual # " + cpuClusterTimes.length);
+                        + mNumCpuClusters + " actual # " + cpuClusterTimes.length);
             }
         }
-        final double cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
 
-        if (DEBUG && (cpuTimeMs != 0 || cpuPowerMah != 0)) {
-            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + cpuTimeMs + " ms power="
-                    + formatCharge(cpuPowerMah));
+        // Additional per-frequency battery drain
+        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+            final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
+                final double power =
+                        mPerCpuFreqPowerEstimators[cluster][speed].calculatePower(timeUs / 1000);
+                if (DEBUG) {
+                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+                            + speed + " timeUs=" + timeUs + " power="
+                            + formatCharge(power));
+                }
+                powerMah += power;
+            }
+        }
+
+        if (DEBUG && (durationMs != 0 || powerMah != 0)) {
+            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + durationMs + " ms power="
+                    + formatCharge(powerMah));
         }
 
         // Keep track of the package with highest drain.
         double highestDrain = 0;
         String packageWithHighestDrain = null;
-        long cpuFgTimeMs = 0;
+        long durationFgMs = 0;
         final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
         final int processStatsCount = processStats.size();
         for (int i = 0; i < processStatsCount; i++) {
             final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
             final String processName = processStats.keyAt(i);
-            cpuFgTimeMs += ps.getForegroundTime(statsType);
+            durationFgMs += ps.getForegroundTime(statsType);
 
             final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
                     + ps.getForegroundTime(statsType);
@@ -107,20 +192,19 @@
             }
         }
 
-
         // Ensure that the CPU times make sense.
-        if (cpuFgTimeMs > cpuTimeMs) {
-            if (DEBUG && cpuFgTimeMs > cpuTimeMs + 10000) {
+        if (durationFgMs > durationMs) {
+            if (DEBUG && durationFgMs > durationMs + 10000) {
                 Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
             }
 
             // Statistics may not have been gathered yet.
-            cpuTimeMs = cpuFgTimeMs;
+            durationMs = durationFgMs;
         }
 
-        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, cpuPowerMah)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, cpuTimeMs)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, cpuFgTimeMs)
-                .setPackageWithHighestDrain(packageWithHighestDrain);
+        result.durationMs = durationMs;
+        result.durationFgMs = durationFgMs;
+        result.powerMah = powerMah;
+        result.packageWithHighestDrain = packageWithHighestDrain;
     }
 }
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index 9ea934a..df25cda 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -45,8 +45,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index dcc8a15..4a4991b 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -49,8 +49,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         if (mPowerMah != 0) {
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index df46058..21dcce9 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -26,8 +26,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index e3bd64d..22001d4 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -85,8 +85,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
 
         PowerAndDuration total = new PowerAndDuration();
 
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 6ab8c90..362ca07 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -39,8 +39,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
         final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 05fcc70..7c45cc0 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -67,17 +67,10 @@
      * @param batteryStats  The recorded battery stats.
      * @param rawRealtimeUs The raw system realtime in microseconds.
      * @param rawUptimeUs   The raw system uptime in microseconds.
-     * @param statsType     The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this
-     *                      can only be {@link BatteryStats#STATS_SINCE_CHARGED}, since
-     *                      {@link BatteryStats#STATS_CURRENT} and
-     *                      {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated.
-     * @param asUsers       An array of users for which the attribution is requested.  It may
-     *                      contain {@link UserHandle#USER_ALL} to indicate that the attribution
-     *                      should be performed for all users.
+     * @param query         The query parameters for the calculator.
      */
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
@@ -99,19 +92,6 @@
      */
     protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                                       long rawUptimeUs, int statsType) {
-
-        // TODO(b/175156498): Temporary code during the transition from BatterySippers to
-        //  BatteryConsumers.
-        UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, u);
-        calculateApp(builder, u, rawRealtimeUs, rawUptimeUs, BatteryUsageStatsQuery.DEFAULT);
-        final UidBatteryConsumer uidBatteryConsumer = builder.build();
-        app.cpuPowerMah = uidBatteryConsumer.getConsumedPower(
-                UidBatteryConsumer.POWER_COMPONENT_CPU);
-        app.cpuTimeMs = uidBatteryConsumer.getUsageDurationMillis(
-                UidBatteryConsumer.TIME_COMPONENT_CPU);
-        app.cpuFgTimeMs = uidBatteryConsumer.getUsageDurationMillis(
-                UidBatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND);
-        app.packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
     }
 
     /**
@@ -163,7 +143,11 @@
         return String.format(Locale.ENGLISH, format, power);
     }
 
-    static double mAhToUJ(long energyUJ) {
+    static double uJtoMah(long energyUJ) {
+        if (energyUJ == 0) {
+            return 0;
+        }
+
         // TODO(b/173765509): Convert properly. This is mJ / V * (h/3600s) = mAh with V = 3.7 fixed.
         //                    Leaving for later since desired units of energy have yet to be decided
         return energyUJ / 1000.0 / 3.7  / 3600;
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index c86c795..c1dd7ce 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -21,7 +21,7 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.SystemBatteryConsumer;
-import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.Slog;
@@ -39,9 +39,17 @@
     private static final String TAG = "ScreenPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
 
+    // Minimum amount of time the screen should be on to start smearing drain to apps
+    public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
+
     private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
     private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
 
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+    }
+
     public ScreenPowerCalculator(PowerProfile powerProfile) {
         mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
                 powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
@@ -51,19 +59,41 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
-        final long durationMs = computeDuration(batteryStats, rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED);
-        final double powerMah = computePower(batteryStats, rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED, durationMs);
-        if (powerMah != 0) {
-            builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
-                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
+        final boolean forceUsePowerProfileModel = (query.getFlags()
+                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;
+
+        final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
+                batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED,
+                forceUsePowerProfileModel);
+
+        builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE,
+                        totalPowerAndDuration.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE,
+                        totalPowerAndDuration.powerMah);
+
+        // Now deal with each app's UidBatteryConsumer. The results are stored in the
+        // BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
+        // but the method depends on the data source.
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        if (useEnergyData) {
+            final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
+            for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+                final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+                calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.getBatteryStatsUid(),
+                        rawRealtimeUs);
+                app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN,
+                                appPowerAndDuration.durationMs)
+                        .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
+                                appPowerAndDuration.powerMah);
+            }
+        } else {
+            smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
+                    rawRealtimeUs);
         }
-        // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
-        // TODO(b/178140704): Attribute (measured/smeared) usage *per app* for BatteryUsageStats.
     }
 
     /**
@@ -72,51 +102,79 @@
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-
-        final long energyUJ = batteryStats.getScreenOnEnergy();
-        final boolean isMeasuredDataAvailable = energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE;
-
-        final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
-        final double powerMah = getMeasuredOrComputedPower(
-                energyUJ, batteryStats, rawRealtimeUs, statsType, durationMs);
-        if (powerMah == 0) {
+        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
+        final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
+                batteryStats, rawRealtimeUs, statsType, false);
+        if (totalPowerAndDuration.powerMah == 0) {
             return;
         }
 
         // First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
         final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
-        bs.usagePowerMah = powerMah;
-        bs.usageTimeMs = durationMs;
+        bs.usagePowerMah = totalPowerAndDuration.powerMah;
+        bs.usageTimeMs = totalPowerAndDuration.durationMs;
         bs.sumPower();
         sippers.add(bs);
 
         // Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
         // field, which is considered smeared, but the method depends on the data source.
-        if (isMeasuredDataAvailable) {
-            super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        if (useEnergyData) {
+            final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
+            for (int i = sippers.size() - 1; i >= 0; i--) {
+                final BatterySipper app = sippers.get(i);
+                if (app.drainType == BatterySipper.DrainType.APP) {
+                    calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj, rawRealtimeUs);
+                    app.screenPowerMah = appPowerAndDuration.powerMah;
+                }
+            }
         } else {
-            smearScreenBatterySipper(sippers, bs);
+            smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
         }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
+    /**
+     * Stores duration and power information in totalPowerAndDuration and returns true if measured
+     * energy data is available and should be used by the model.
+     */
+    private boolean calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
+            BatteryStats batteryStats, long rawRealtimeUs, int statsType,
+            boolean forceUsePowerProfileModel) {
+        totalPowerAndDuration.durationMs = calculateDuration(batteryStats, rawRealtimeUs,
+                statsType);
+
+        if (!forceUsePowerProfileModel) {
+            final long energyUJ = batteryStats.getScreenOnEnergy();
+            if (energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+                totalPowerAndDuration.powerMah = uJtoMah(energyUJ);
+                return true;
+            }
+        }
+
+        totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
+                rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
+        return false;
+    }
+
+    private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
+            BatteryStats.Uid u, long rawRealtimeUs) {
+        appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);
+
         final long energyUJ = u.getScreenOnEnergy();
         if (energyUJ < 0) {
             Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
+            appPowerAndDuration.powerMah = 0;
             return;
         }
-        if (energyUJ == 0) return;
-        app.screenPowerMah = mAhToUJ(u.getScreenOnEnergy());
+
+        appPowerAndDuration.powerMah = uJtoMah(energyUJ);
     }
 
-    private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+    private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
         return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
     }
 
-    private double computePower(BatteryStats batteryStats, long rawRealtimeUs, int statsType,
-            long durationMs) {
+    private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs,
+            int statsType, long durationMs) {
         double power = mScreenOnPowerEstimator.calculatePower(durationMs);
         for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             final long brightnessTime =
@@ -132,35 +190,25 @@
         return power;
     }
 
-    private double getMeasuredOrComputedPower(long measuredEnergyUJ,
-            BatteryStats batteryStats, long rawRealtimeUs, int statsType, long durationMs) {
-
-        if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
-            return mAhToUJ(measuredEnergyUJ);
-        } else {
-            return computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
-        }
-    }
-
     /**
      * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
      * time, and store this in the {@link BatterySipper#screenPowerMah} field.
      */
     @VisibleForTesting
-    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
-
+    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper,
+            long rawRealtimeUs) {
         long totalActivityTimeMs = 0;
         final SparseLongArray activityTimeArray = new SparseLongArray();
         for (int i = sippers.size() - 1; i >= 0; i--) {
             final BatteryStats.Uid uid = sippers.get(i).uidObj;
             if (uid != null) {
-                final long timeMs = getProcessForegroundTimeMs(uid);
+                final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
                 activityTimeArray.put(uid.getUid(), timeMs);
                 totalActivityTimeMs += timeMs;
             }
         }
 
-        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+        if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
             final double totalScreenPowerMah = screenSipper.totalPowerMah;
             for (int i = sippers.size() - 1; i >= 0; i--) {
                 final BatterySipper sipper = sippers.get(i);
@@ -171,10 +219,37 @@
         }
     }
 
+    /**
+     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
+     * time, and store this in the {@link BatterySipper#screenPowerMah} field.
+     */
+    private void smearScreenBatteryDrain(
+            SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders,
+            PowerAndDuration totalPowerAndDuration, long rawRealtimeUs) {
+        long totalActivityTimeMs = 0;
+        final SparseLongArray activityTimeArray = new SparseLongArray();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final BatteryStats.Uid uid = uidBatteryConsumerBuilders.valueAt(i).getBatteryStatsUid();
+            final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
+            activityTimeArray.put(uid.getUid(), timeMs);
+            totalActivityTimeMs += timeMs;
+        }
+
+        if (totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
+            final double totalScreenPowerMah = totalPowerAndDuration.powerMah;
+            for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+                final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+                final long durationMs = activityTimeArray.get(app.getUid(), 0);
+                final double powerMah = totalScreenPowerMah * durationMs / totalActivityTimeMs;
+                app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN, durationMs)
+                        .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah);
+            }
+        }
+    }
+
     /** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
     @VisibleForTesting
-    public long getProcessForegroundTimeMs(BatteryStats.Uid uid) {
-        final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000;
+    public long getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs) {
         final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};
 
         long timeUs = 0;
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index fbad75e..3ed59f1 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -110,4 +110,24 @@
 
         return mDeltaCpuThreadTimes;
     }
+
+    /** Returns CPU times, per thread group, since tracking started. */
+    @Nullable
+    public SystemServiceCpuThreadTimes readAbsolute() {
+        final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
+        final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
+                mKernelCpuThreadReader.getProcessCpuUsage();
+        if (processCpuUsage == null) {
+            return null;
+        }
+        final SystemServiceCpuThreadTimes result = new SystemServiceCpuThreadTimes();
+        result.threadCpuTimesUs = new long[numCpuFrequencies];
+        result.binderThreadCpuTimesUs = new long[numCpuFrequencies];
+        for (int i = 0; i < numCpuFrequencies; ++i) {
+            result.threadCpuTimesUs[i] = processCpuUsage.threadCpuTimesMillis[i] * 1_000;
+            result.binderThreadCpuTimesUs[i] =
+                    processCpuUsage.selectedThreadCpuTimesMillis[i] * 1_000;
+        }
+        return result;
+    }
 }
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index 55fc1bb..955f6af 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -51,10 +51,9 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         calculateSystemServicePower(batteryStats);
-        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query, asUsers);
+        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index 53f8515..8e80286 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -17,10 +17,15 @@
 package com.android.internal.os;
 
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.SparseArray;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.util.List;
 
 /**
@@ -29,6 +34,33 @@
 public class UserPowerCalculator extends PowerCalculator {
 
     @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final int[] userIds = query.getUserIds();
+        if (ArrayUtils.contains(userIds, UserHandle.USER_ALL)) {
+            return;
+        }
+
+        SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            UidBatteryConsumer.Builder uidBuilder = uidBatteryConsumerBuilders.valueAt(i);
+            final int uid = uidBuilder.getUid();
+            if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+                continue;
+            }
+
+            final int userId = UserHandle.getUserId(uid);
+            if (!ArrayUtils.contains(userIds, userId)) {
+                uidBuilder.excludeFromBatteryUsageStats();
+                builder.getOrCreateUserBatteryConsumerBuilder(userId)
+                        .addUidBatteryConsumer(uidBuilder);
+            }
+        }
+    }
+
+    @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
         final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
diff --git a/core/java/com/android/internal/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java
deleted file mode 100644
index 1f6dc26..0000000
--- a/core/java/com/android/internal/power/MeasuredEnergyArray.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 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.power;
-
-
-import android.annotation.IntDef;
-
-import com.android.internal.os.RailStats;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Interface to provide subsystem energy data.
- * TODO: replace this and {@link RailStats} once b/173077356 is done
- */
-public interface MeasuredEnergyArray {
-    int SUBSYSTEM_UNKNOWN = -1;
-    int SUBSYSTEM_DISPLAY = 0;
-    int NUMBER_SUBSYSTEMS = 1;
-    String[] SUBSYSTEM_NAMES = {"display"};
-
-
-    @IntDef(prefix = { "SUBSYSTEM_" }, value = {
-            SUBSYSTEM_UNKNOWN,
-            SUBSYSTEM_DISPLAY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface MeasuredEnergySubsystem {}
-
-    /**
-     * Get the subsystem at an index in array.
-     *
-     * @param index into the array.
-     * @return subsystem.
-     */
-    @MeasuredEnergySubsystem
-    int getSubsystem(int index);
-
-    /**
-     * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
-     *
-     * @param index into the array.
-     * @return energy (in microjoules) consumed since boot.
-     */
-    long getEnergy(int index);
-
-    /**
-     * Return number of subsystems in the array.
-     */
-    int size();
-}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e310f8d..38ef55c 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -28,7 +28,6 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -247,7 +246,7 @@
     }
 
     /**
-     * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+     * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
      */
     public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
         if (Display.isOnState(screenState)) {
@@ -450,7 +449,8 @@
         return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
     }
 
-    private boolean isValidCustomBucket(int customBucket) {
+    /** Returns whether the given custom bucket is valid (exists) on this device. */
+    public boolean isValidCustomBucket(int customBucket) {
         return customBucket >= 0
                 && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
     }
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 488b18d..c6a040c 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -47,6 +47,13 @@
     private CollectionUtils() { /* cannot be instantiated */ }
 
     /**
+     * @see Collection#contains(Object)
+     */
+    public static <T> boolean contains(@Nullable Collection<T> collection, T element) {
+        return collection != null && collection.contains(element);
+    }
+
+    /**
      * Returns a list of items from the provided list that match the given condition.
      *
      * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
new file mode 100644
index 0000000..6cc5a4a
--- /dev/null
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 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.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.app.Person;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * A custom-built layout for the Notification.CallStyle.
+ */
+@RemoteViews.RemoteView
+public class CallLayout extends FrameLayout {
+    private final PeopleHelper mPeopleHelper = new PeopleHelper();
+
+    private int mLayoutColor;
+    private Icon mLargeIcon;
+    private Person mUser;
+
+    private CachingIconView mConversationIconView;
+    private CachingIconView mIcon;
+    private CachingIconView mConversationIconBadgeBg;
+    private TextView mConversationText;
+
+    public CallLayout(@NonNull Context context) {
+        super(context);
+    }
+
+    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mPeopleHelper.init(getContext());
+        mConversationText = findViewById(R.id.conversation_text);
+        mConversationIconView = findViewById(R.id.conversation_icon);
+        mIcon = findViewById(R.id.icon);
+        mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
+
+        // When the small icon is gone, hide the rest of the badge
+        mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
+            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+        });
+    }
+
+    private void updateCallLayout() {
+        CharSequence callerName = "";
+        String symbol = "";
+        Icon icon = null;
+        if (mUser != null) {
+            icon = mUser.getIcon();
+            callerName = mUser.getName();
+            symbol = mPeopleHelper.findNamePrefix(callerName, "");
+        }
+        if (icon == null) {
+            icon = mLargeIcon;
+        }
+        if (icon == null) {
+            icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor);
+        }
+        // TODO(b/179178086): crop/clip the icon to a circle?
+        mConversationIconView.setImageIcon(icon);
+        mConversationText.setText(callerName);
+    }
+
+    @RemotableViewMethod
+    public void setLayoutColor(int color) {
+        mLayoutColor = color;
+    }
+
+    /**
+     * @param color the color of the notification background
+     */
+    @RemotableViewMethod
+    public void setNotificationBackgroundColor(int color) {
+        mConversationIconBadgeBg.setImageTintList(ColorStateList.valueOf(color));
+    }
+
+    @RemotableViewMethod
+    public void setLargeIcon(Icon largeIcon) {
+        mLargeIcon = largeIcon;
+    }
+
+    /**
+     * Set the notification extras so that this layout has access
+     */
+    @RemotableViewMethod
+    public void setData(Bundle extras) {
+        setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON));
+        updateCallLayout();
+    }
+
+    private void setUser(Person user) {
+        mUser = user;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 40e671f..1b1e0bf 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -18,8 +18,6 @@
 
 import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
 import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -34,10 +32,7 @@
 import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.GradientDrawable;
@@ -68,14 +63,12 @@
 
 import com.android.internal.R;
 import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.ContrastColorUtil;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.function.Consumer;
-import java.util.regex.Pattern;
 
 /**
  * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
@@ -85,16 +78,6 @@
 public class ConversationLayout extends FrameLayout
         implements ImageMessageConsumer, IMessagingLayout {
 
-    private static final float COLOR_SHIFT_AMOUNT = 60;
-    /**
-     *  Pattern for filter some ignorable characters.
-     *  p{Z} for any kind of whitespace or invisible separator.
-     *  p{C} for any kind of punctuation character.
-     */
-    private static final Pattern IGNORABLE_CHAR_PATTERN
-            = Pattern.compile("[\\p{C}\\p{Z}]");
-    private static final Pattern SPECIAL_CHAR_PATTERN
-            = Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
     private static final Consumer<MessagingMessage> REMOVE_MESSAGE
             = MessagingMessage::removeMessage;
     public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
@@ -106,6 +89,7 @@
     public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
     public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
     public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
+    private final PeopleHelper mPeopleHelper = new PeopleHelper();
     private List<MessagingMessage> mMessages = new ArrayList<>();
     private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
     private MessagingLinearLayout mMessagingLinearLayout;
@@ -114,9 +98,6 @@
     private int mLayoutColor;
     private int mSenderTextColor;
     private int mMessageTextColor;
-    private int mAvatarSize;
-    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private Paint mTextPaint = new Paint();
     private Icon mAvatarReplacement;
     private boolean mIsOneToOne;
     private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
@@ -196,6 +177,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mPeopleHelper.init(getContext());
         mMessagingLinearLayout = findViewById(R.id.notification_messaging);
         mActions = findViewById(R.id.actions);
         mImageMessageContainer = findViewById(R.id.conversation_image_message_container);
@@ -205,9 +187,6 @@
         int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
         mMessagingClipRect = new Rect(0, 0, size, size);
         setMessagingClippingDisabled(false);
-        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
-        mTextPaint.setTextAlign(Paint.Align.CENTER);
-        mTextPaint.setAntiAlias(true);
         mConversationIconView = findViewById(R.id.conversation_icon);
         mConversationIconContainer = findViewById(R.id.conversation_icon_container);
         mIcon = findViewById(R.id.icon);
@@ -250,15 +229,15 @@
         });
         // When the small icon is gone, hide the rest of the badge
         mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
-            animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
-            animateViewForceHidden(mImportanceRingView, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
         });
 
         // When the conversation icon is gone, hide the whole badge
         mConversationIconView.setOnForceHiddenChangedListener((forceHidden) -> {
-            animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
-            animateViewForceHidden(mImportanceRingView, forceHidden);
-            animateViewForceHidden(mIcon, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
         });
         mConversationText = findViewById(R.id.conversation_text);
         mExpandButtonContainer = findViewById(R.id.expand_button_container);
@@ -317,28 +296,6 @@
                 R.dimen.notification_header_separating_margin);
     }
 
-    private void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
-        boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
-        if (forceHidden == nowForceHidden) {
-            // We are either already forceHidden or will be
-            return;
-        }
-        view.animate().cancel();
-        view.setWillBeForceHidden(forceHidden);
-        view.animate()
-                .scaleX(forceHidden ? 0.5f : 1.0f)
-                .scaleY(forceHidden ? 0.5f : 1.0f)
-                .alpha(forceHidden ? 0.0f : 1.0f)
-                .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
-                .setDuration(160);
-        if (view.getVisibility() != VISIBLE) {
-            view.setForceHidden(forceHidden);
-        } else {
-            view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
-        }
-        view.animate().start();
-    }
-
     @RemotableViewMethod
     public void setAvatarReplacement(Icon icon) {
         mAvatarReplacement = icon;
@@ -561,7 +518,8 @@
                     if (mConversationIcon == null) {
                         Icon avatarIcon = messagingGroup.getAvatarIcon();
                         if (avatarIcon == null) {
-                            avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
+                            avatarIcon = mPeopleHelper.createAvatarSymbol(conversationText, "",
+                                    mLayoutColor);
                         }
                         mConversationIcon = avatarIcon;
                     }
@@ -674,11 +632,11 @@
             }
         }
         if (lastIcon == null) {
-            lastIcon = createAvatarSymbol(" ", "", mLayoutColor);
+            lastIcon = mPeopleHelper.createAvatarSymbol(" ", "", mLayoutColor);
         }
         bottomView.setImageIcon(lastIcon);
         if (secondLastIcon == null) {
-            secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
+            secondLastIcon = mPeopleHelper.createAvatarSymbol("", "", mLayoutColor);
         }
         topView.setImageIcon(secondLastIcon);
     }
@@ -838,8 +796,10 @@
     }
 
     private void updateTitleAndNamesDisplay() {
+        // Map of unique names to their prefix
         ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
-        ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+        // Map of single-character string prefix to the only name which uses it, or null if multiple
+        ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
         for (int i = 0; i < mGroups.size(); i++) {
             MessagingGroup group = mGroups.get(i);
             CharSequence senderName = group.getSenderName();
@@ -847,22 +807,22 @@
                 continue;
             }
             if (!uniqueNames.containsKey(senderName)) {
-                // Only use visible characters to get uniqueNames
-                String pureSenderName = IGNORABLE_CHAR_PATTERN
-                        .matcher(senderName).replaceAll("" /* replacement */);
-                char c = pureSenderName.charAt(0);
-                if (uniqueCharacters.containsKey(c)) {
+                String charPrefix = mPeopleHelper.findNamePrefix(senderName, null);
+                if (charPrefix == null) {
+                    continue;
+                }
+                if (uniqueCharacters.containsKey(charPrefix)) {
                     // this character was already used, lets make it more unique. We first need to
                     // resolve the existing character if it exists
-                    CharSequence existingName = uniqueCharacters.get(c);
+                    CharSequence existingName = uniqueCharacters.get(charPrefix);
                     if (existingName != null) {
-                        uniqueNames.put(existingName, findNameSplit((String) existingName));
-                        uniqueCharacters.put(c, null);
+                        uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
+                        uniqueCharacters.put(charPrefix, null);
                     }
-                    uniqueNames.put(senderName, findNameSplit((String) senderName));
+                    uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
                 } else {
-                    uniqueNames.put(senderName, Character.toString(c));
-                    uniqueCharacters.put(c, pureSenderName);
+                    uniqueNames.put(senderName, charPrefix);
+                    uniqueCharacters.put(charPrefix, senderName);
                 }
             }
         }
@@ -898,8 +858,8 @@
             } else {
                 Icon cachedIcon = cachedAvatars.get(senderName);
                 if (cachedIcon == null) {
-                    cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
-                            mLayoutColor);
+                    cachedIcon = mPeopleHelper.createAvatarSymbol(senderName,
+                            uniqueNames.get(senderName), mLayoutColor);
                     cachedAvatars.put(senderName, cachedIcon);
                 }
                 group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
@@ -908,49 +868,6 @@
         }
     }
 
-    private Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
-        if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) ||
-                SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
-            Icon avatarIcon = Icon.createWithResource(getContext(),
-                    R.drawable.messaging_user);
-            avatarIcon.setTint(findColor(senderName, layoutColor));
-            return avatarIcon;
-        } else {
-            Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(bitmap);
-            float radius = mAvatarSize / 2.0f;
-            int color = findColor(senderName, layoutColor);
-            mPaint.setColor(color);
-            canvas.drawCircle(radius, radius, radius, mPaint);
-            boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
-            mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
-            mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
-            int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
-            canvas.drawText(symbol, radius, yPos, mTextPaint);
-            return Icon.createWithBitmap(bitmap);
-        }
-    }
-
-    private int findColor(CharSequence senderName, int layoutColor) {
-        double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
-        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
-
-        // we need to offset the range if the luminance is too close to the borders
-        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
-        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
-        return ContrastColorUtil.getShiftedColor(layoutColor,
-                (int) (shift * COLOR_SHIFT_AMOUNT));
-    }
-
-    private String findNameSplit(String existingName) {
-        String[] split = existingName.split(" ");
-        if (split.length > 1) {
-            return Character.toString(split[0].charAt(0))
-                    + Character.toString(split[1].charAt(0));
-        }
-        return existingName.substring(0, 1);
-    }
-
     @RemotableViewMethod
     public void setLayoutColor(int color) {
         mLayoutColor = color;
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 5213746..058a921 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,17 +16,24 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableWrapper;
 import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
+import android.view.ViewGroup;
 import android.widget.Button;
+import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 
+import com.android.internal.R;
+
 /**
  * A button implementation for the emphasized notification style.
  *
@@ -37,6 +44,7 @@
     private final RippleDrawable mRipple;
     private final int mStrokeWidth;
     private final int mStrokeColor;
+    private boolean mPriority;
 
     public EmphasizedNotificationButton(Context context) {
         this(context, null);
@@ -80,4 +88,57 @@
         inner.setStroke(hasStroke ? mStrokeWidth : 0, mStrokeColor);
         invalidate();
     }
+
+    /**
+     * Sets an image icon which will have its size constrained and will be set to the same color as
+     * the text. Must be called after {@link #setTextColor(int)} for the latter to work.
+     */
+    @RemotableViewMethod(asyncImpl = "setImageIconAsync")
+    public void setImageIcon(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        setImageDrawable(drawable);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable setImageIconAsync(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        return () -> setImageDrawable(drawable);
+    }
+
+    private void setImageDrawable(Drawable drawable) {
+        if (drawable != null) {
+            drawable.mutate();
+            drawable.setTintList(getTextColors());
+            drawable.setTintBlendMode(BlendMode.SRC_IN);
+            int iconSize = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.notification_actions_icon_drawable_size);
+            drawable.setBounds(0, 0, iconSize, iconSize);
+        }
+        setCompoundDrawablesRelative(drawable, null, null, null);
+    }
+
+    /**
+     * Changes the LayoutParams.width to WRAP_CONTENT, with the argument representing if this view
+     * is a priority over its peers (which affects weight).
+     */
+    @RemotableViewMethod
+    public void setWrapModePriority(boolean priority) {
+        mPriority = priority;
+        ViewGroup.LayoutParams layoutParams = getLayoutParams();
+        layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        if (layoutParams instanceof LinearLayout.LayoutParams) {
+            ((LinearLayout.LayoutParams) layoutParams).weight = 0;
+        }
+        setLayoutParams(layoutParams);
+    }
+
+    /**
+     * Sizing this button is a priority compared with its peers.
+     */
+    public boolean isPriority() {
+        return mPriority;
+    }
 }
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index c7ea781..8e6497b 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.widget;
 
+import android.annotation.DimenRes;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.RippleDrawable;
@@ -41,13 +42,16 @@
 
     private final int mGravity;
     private int mTotalWidth = 0;
+    private int mExtraStartPadding = 0;
     private ArrayList<Pair<Integer, TextView>> mMeasureOrderTextViews = new ArrayList<>();
     private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
     private boolean mEmphasizedMode;
+    private boolean mPrioritizedWrapMode;
     private int mDefaultPaddingBottom;
     private int mDefaultPaddingTop;
     private int mEmphasizedHeight;
     private int mRegularHeight;
+    @DimenRes private int mCollapsibleIndentDimen;
 
     public NotificationActionListLayout(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -68,7 +72,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mEmphasizedMode) {
+        if (mEmphasizedMode && !mPrioritizedWrapMode) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
         }
@@ -151,7 +155,15 @@
             measuredChildren++;
         }
 
-        mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft;
+        int collapsibleIndent = mCollapsibleIndentDimen == 0 ? 0
+                : getResources().getDimensionPixelOffset(mCollapsibleIndentDimen);
+        if (innerWidth - usedWidth > collapsibleIndent) {
+            mExtraStartPadding = collapsibleIndent;
+        } else {
+            mExtraStartPadding = 0;
+        }
+
+        mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
         setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                 resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
     }
@@ -163,7 +175,11 @@
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View c = getChildAt(i);
-            if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
+            if (c instanceof EmphasizedNotificationButton
+                    && ((EmphasizedNotificationButton) c).isPriority()) {
+                // add with 0 length to ensure that this view is measured before others.
+                mMeasureOrderTextViews.add(Pair.create(0, (TextView) c));
+            } else if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
                 mMeasureOrderTextViews.add(Pair.create(((TextView) c).getText().length(),
                         (TextView)c));
             } else {
@@ -197,7 +213,7 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (mEmphasizedMode) {
+        if (mEmphasizedMode && !mPrioritizedWrapMode) {
             super.onLayout(changed, left, top, right, bottom);
             return;
         }
@@ -214,6 +230,9 @@
             int absoluteGravity = Gravity.getAbsoluteGravity(Gravity.START, getLayoutDirection());
             if (absoluteGravity == Gravity.RIGHT) {
                 childLeft += right - left - mTotalWidth;
+            } else {
+                // Put the extra start padding (if any) on the left when LTR
+                childLeft += mExtraStartPadding;
             }
         }
 
@@ -274,6 +293,26 @@
     }
 
     /**
+     * When used with emphasizedMode, changes the button sizing behavior to prioritize certain
+     * buttons (which are system generated) to not scrunch, and leave the remaining space for
+     * custom actions.
+     */
+    @RemotableViewMethod
+    public void setPrioritizedWrapMode(boolean prioritizedWrapMode) {
+        mPrioritizedWrapMode = prioritizedWrapMode;
+    }
+
+    /**
+     * When buttons are in wrap mode, this is a padding that will be applied at the start of the
+     * layout of the actions, but only when those actions would fit with the entire padding
+     * visible.  Otherwise, this padding will be omitted entirely.
+     */
+    @RemotableViewMethod
+    public void setCollapsibleIndentDimen(@DimenRes int collapsibleIndentDimen) {
+        mCollapsibleIndentDimen = collapsibleIndentDimen;
+    }
+
+    /**
      * Set whether the list is in a mode where some actions are emphasized. This will trigger an
      * equal measuring where all actions are full height and change a few parameters like
      * the padding.
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
new file mode 100644
index 0000000..77f4c8f
--- /dev/null
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2021 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.widget;
+
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.ContrastColorUtil;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class provides some methods used by both the {@link ConversationLayout} and
+ * {@link CallLayout} which both use the visual design originally created for conversations in R.
+ */
+public class PeopleHelper {
+
+    private static final float COLOR_SHIFT_AMOUNT = 60;
+    /**
+     * Pattern for filter some ignorable characters.
+     * p{Z} for any kind of whitespace or invisible separator.
+     * p{C} for any kind of punctuation character.
+     */
+    private static final Pattern IGNORABLE_CHAR_PATTERN = Pattern.compile("[\\p{C}\\p{Z}]");
+    private static final Pattern SPECIAL_CHAR_PATTERN =
+            Pattern.compile("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
+
+    private Context mContext;
+    private int mAvatarSize;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mTextPaint = new Paint();
+
+    /**
+     * Call this when the view is inflated to provide a context and initialize the helper
+     */
+    public void init(Context context) {
+        mContext = context;
+        mAvatarSize = context.getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        mTextPaint.setAntiAlias(true);
+    }
+
+    /**
+     * A utility for animating CachingIconViews away when hidden.
+     */
+    public void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
+        boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
+        if (forceHidden == nowForceHidden) {
+            // We are either already forceHidden or will be
+            return;
+        }
+        view.animate().cancel();
+        view.setWillBeForceHidden(forceHidden);
+        view.animate()
+                .scaleX(forceHidden ? 0.5f : 1.0f)
+                .scaleY(forceHidden ? 0.5f : 1.0f)
+                .alpha(forceHidden ? 0.0f : 1.0f)
+                .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
+                .setDuration(160);
+        if (view.getVisibility() != View.VISIBLE) {
+            view.setForceHidden(forceHidden);
+        } else {
+            view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
+        }
+        view.animate().start();
+    }
+
+    /**
+     * This creates an avatar symbol for the given person or group
+     *
+     * @param name        the name of the person or group
+     * @param symbol      a pre-chosen symbol for the person or group.  See
+     *                    {@link #findNamePrefix(CharSequence, String)} or
+     *                    {@link #findNameSplit(CharSequence)}
+     * @param layoutColor the background color of the layout
+     */
+    @NonNull
+    public Icon createAvatarSymbol(@NonNull CharSequence name, @NonNull String symbol,
+            @ColorInt int layoutColor) {
+        if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol)
+                || SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
+            Icon avatarIcon = Icon.createWithResource(mContext, R.drawable.messaging_user);
+            avatarIcon.setTint(findColor(name, layoutColor));
+            return avatarIcon;
+        } else {
+            Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            float radius = mAvatarSize / 2.0f;
+            int color = findColor(name, layoutColor);
+            mPaint.setColor(color);
+            canvas.drawCircle(radius, radius, radius, mPaint);
+            boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+            mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+            mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
+            int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
+            canvas.drawText(symbol, radius, yPos, mTextPaint);
+            return Icon.createWithBitmap(bitmap);
+        }
+    }
+
+    private int findColor(@NonNull CharSequence senderName, int layoutColor) {
+        double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
+        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+        // we need to offset the range if the luminance is too close to the borders
+        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+        return ContrastColorUtil.getShiftedColor(layoutColor,
+                (int) (shift * COLOR_SHIFT_AMOUNT));
+    }
+
+    /**
+     * Get the name with whitespace and punctuation characters removed
+     */
+    private String getPureName(@NonNull CharSequence name) {
+        return IGNORABLE_CHAR_PATTERN.matcher(name).replaceAll("" /* replacement */);
+    }
+
+    /**
+     * Gets a single character string prefix name for the person or group
+     *
+     * @param name     the name of the person or group
+     * @param fallback the string to return if the name has no usable characters
+     */
+    public String findNamePrefix(@NonNull CharSequence name, String fallback) {
+        String pureName = getPureName(name);
+        if (pureName.isEmpty()) {
+            return fallback;
+        }
+        try {
+            return new String(Character.toChars(pureName.codePointAt(0)));
+        } catch (RuntimeException ignore) {
+            return fallback;
+        }
+    }
+
+    /**
+     * Find a 1 or 2 character prefix name for the person or group
+     */
+    public String findNameSplit(@NonNull CharSequence name) {
+        String nameString = name instanceof String ? ((String) name) : name.toString();
+        String[] split = nameString.trim().split("[ ]+");
+        if (split.length > 1) {
+            String first = findNamePrefix(split[0], null);
+            String second = findNamePrefix(split[1], null);
+            if (first != null && second != null) {
+                return first + second;
+            }
+        }
+        return findNamePrefix(name, "");
+    }
+}
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index f1998a5..c7439f1 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -188,7 +188,7 @@
   ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
 
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
-  std::unique_ptr<const ApkAssets> apk_assets;
+  std::unique_ptr<ApkAssets> apk_assets;
   switch (format) {
     case FORMAT_APK: {
       auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
@@ -354,7 +354,7 @@
 }
 
 static jlong NativeGetFinalizer(JNIEnv* /*env*/, jclass /*clazz*/) {
-  return reinterpret_cast<jlong>(&NativeDestroy);
+  return static_cast<jlong>(reinterpret_cast<uintptr_t>(&NativeDestroy));
 }
 
 static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index bb51c57..c64174b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -43,6 +43,7 @@
 #include <nativehelper/JNIPlatformHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
 #include <dmabufinfo/dmabufinfo.h>
 #include <meminfo/procmeminfo.h>
 #include <meminfo/sysmeminfo.h>
@@ -805,6 +806,16 @@
     return heapsSizeKb;
 }
 
+static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject clazz) {
+    jlong dmabufTotalSizeKb = -1;
+    uint64_t size;
+
+    if (dmabufinfo::GetDmabufTotalExportedKb(&size)) {
+        dmabufTotalSizeKb = size;
+    }
+    return dmabufTotalSizeKb;
+}
+
 static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
     jlong poolsSizeKb = -1;
     uint64_t size;
@@ -816,8 +827,19 @@
     return poolsSizeKb;
 }
 
-static jlong android_os_Debug_getIonMappedSizeKb(JNIEnv* env, jobject clazz) {
-    jlong ionPss = 0;
+static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject clazz) {
+    jlong poolsSizeKb = -1;
+    uint64_t size;
+
+    if (meminfo::ReadDmabufHeapPoolsSizeKb(&size)) {
+        poolsSizeKb = size;
+    }
+
+    return poolsSizeKb;
+}
+
+static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
+    jlong dmabufPss = 0;
     std::vector<dmabufinfo::DmaBuffer> dmabufs;
 
     std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
@@ -841,10 +863,10 @@
     }
 
     for (const dmabufinfo::DmaBuffer& buf : dmabufs) {
-        ionPss += buf.size() / 1024;
+        dmabufPss += buf.size() / 1024;
     }
 
-    return ionPss;
+    return dmabufPss;
 }
 
 static jlong android_os_Debug_getGpuTotalUsageKb(JNIEnv* env, jobject clazz) {
@@ -922,10 +944,14 @@
             (void*)android_os_Debug_getFreeZramKb },
     { "getIonHeapsSizeKb", "()J",
             (void*)android_os_Debug_getIonHeapsSizeKb },
+    { "getDmabufTotalExportedKb", "()J",
+            (void*)android_os_Debug_getDmabufTotalExportedKb },
     { "getIonPoolsSizeKb", "()J",
             (void*)android_os_Debug_getIonPoolsSizeKb },
-    { "getIonMappedSizeKb", "()J",
-            (void*)android_os_Debug_getIonMappedSizeKb },
+    { "getDmabufMappedSizeKb", "()J",
+            (void*)android_os_Debug_getDmabufMappedSizeKb },
+    { "getDmabufHeapPoolsSizeKb", "()J",
+            (void*)android_os_Debug_getDmabufHeapPoolsSizeKb },
     { "getGpuTotalUsageKb", "()J",
             (void*)android_os_Debug_getGpuTotalUsageKb },
     { "isVmapStack", "()Z",
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index ac680f6..9746a07 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -199,7 +199,13 @@
     for (;;) {
         uint32_t publishedSeq;
         bool handled;
-        status_t status = mInputPublisher.receiveFinishedSignal(&publishedSeq, &handled);
+        std::function<void(uint32_t seq, bool handled, nsecs_t consumeTime)> callback =
+                [&publishedSeq, &handled](uint32_t inSeq, bool inHandled,
+                                          nsecs_t inConsumeTime) -> void {
+            publishedSeq = inSeq;
+            handled = inHandled;
+        };
+        status_t status = mInputPublisher.receiveFinishedSignal(callback);
         if (status) {
             if (status == WOULD_BLOCK) {
                 return OK;
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 191f748..0aac07d 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -51,19 +51,12 @@
     client->decStrong((void*)nativeCreate);
 }
 
-static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) {
-    SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
-    client->dispose();
-}
-
 static const JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
     { "nativeCreate", "()J",
             (void*)nativeCreate },
     { "nativeDestroy", "(J)V",
             (void*)nativeDestroy },
-    { "nativeKill", "(J)V",
-            (void*)nativeKill }
 };
 
 int register_android_view_SurfaceSession(JNIEnv* env) {
diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto
index 6794c8c..0041f19 100644
--- a/core/proto/android/net/networkrequest.proto
+++ b/core/proto/android/net/networkrequest.proto
@@ -63,6 +63,9 @@
         // higher-scoring network will not go into the background immediately,
         // but will linger and go into the background after the linger timeout.
         TYPE_BACKGROUND_REQUEST = 5;
+        // Like TRACK_DEFAULT, but tracks the system default network, instead of
+        // the default network of the calling application.
+        TYPE_TRACK_SYSTEM_DEFAULT = 6;
     }
     // The type of the request. This is only used by the system and is always
     // NONE elsewhere.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 827bf7b..99014c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1055,6 +1055,14 @@
         android:description="@string/permdesc_accessImsCallService"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+         Only granted if the application is a system app AND is in the Default SMS Role.
+         The permission is revoked when the app is taken out of the Default SMS Role.
+        <p>Protection level: internal|role
+    -->
+    <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to read the user's call log.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #READ_CONTACTS} permission and <em>both</em> your <a
@@ -2698,11 +2706,11 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature|appop|installer|recents|appPredictor|pre23|development -->
+         <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|appop|installer|recents|appPredictor|pre23|development" />
+        android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
 
     <!-- @SystemApi @hide Allows an application to create windows using the type
          {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
@@ -3994,6 +4002,11 @@
     <permission android:name="android.permission.MOVE_PACKAGE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @TestApi Allows an application to keep uninstalled packages as apks.
+         @hide -->
+    <permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to change whether an application component (other than its own) is
          enabled or not.
          <p>Not for use by third-party applications. -->
@@ -5447,6 +5460,11 @@
     <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
                 android:protectionLevel="signature" />
 
+    <!-- Allows managing the Game Mode
+     @hide Used internally. -->
+    <permission android:name="android.permission.MANAGE_GAME_MODE"
+                android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/color/btn_leanback_color.xml b/core/res/res/color/btn_leanback_color.xml
new file mode 100644
index 0000000..012df60
--- /dev/null
+++ b/core/res/res/color/btn_leanback_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+        android:color="@color/btn_leanback_focused" />
+    <item android:state_enabled="false"
+        android:color="@android:color/transparent" />
+    <item android:color="@color/btn_leanback_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/btn_leanback_text_color.xml b/core/res/res/color/btn_leanback_text_color.xml
new file mode 100644
index 0000000..df0df94
--- /dev/null
+++ b/core/res/res/color/btn_leanback_text_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/btn_text_leanback_focused" android:state_focused="true"/>
+    <item android:color="@color/btn_text_leanback_unfocused"/>
+</selector>
diff --git a/core/res/res/drawable/btn_leanback.xml b/core/res/res/drawable/btn_leanback.xml
new file mode 100644
index 0000000..9a63986
--- /dev/null
+++ b/core/res/res/drawable/btn_leanback.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/btn_leanback_color"/>
+    <corners android:radius="@dimen/leanback_button_radius"/>
+</shape>
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 1a574fe..ad68054 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -23,7 +23,7 @@
     <ripple android:color="?attr/colorControlHighlight">
         <item>
             <shape android:shape="rectangle">
-                <corners android:radius="?attr/buttonCornerRadius" />
+                <corners android:radius="@dimen/notification_action_button_radius" />
                 <padding android:left="@dimen/button_padding_horizontal_material"
                          android:top="@dimen/button_padding_vertical_material"
                          android:right="@dimen/button_padding_horizontal_material"
diff --git a/core/res/res/drawable/ic_call_answer.xml b/core/res/res/drawable/ic_call_answer.xml
new file mode 100644
index 0000000..77c0ad1
--- /dev/null
+++ b/core/res/res/drawable/ic_call_answer.xml
@@ -0,0 +1,34 @@
+<!--
+     Copyright (C) 2021 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M6.0168,3.3333L6.5751,5.575L4.6668,7.4916C3.8751,5.7166 3.6001,4.1916
+            3.4834,3.3333H6.0168ZM14.4251,13.375L16.6668,13.9416V16.5166C15.8084,16.4 14.2668,16.125
+            12.4834,15.325L14.4251,13.375ZM6.3418,1.6666H2.5668C2.0918,1.6666 1.7001,2.0666
+            1.7334,2.5416C2.4834,12.875 11.7668,18.275 17.5168,18.275C17.9668,18.275 18.3334,17.9
+            18.3334,17.4416V13.6166C18.3334,13.0416 17.9418,12.5416
+            17.3834,12.4083L14.5918,11.7083C14.2251,11.6166 13.7584,11.6833
+            13.4084,12.0333L10.9251,14.5166C8.6751,13.1833 6.7918,11.3
+            5.4668,9.0416L7.9168,6.5916C8.2251,6.2833 8.3501,5.8333
+            8.2418,5.4083L7.5584,2.6166C7.4168,2.0583 6.9168,1.6666 6.3418,1.6666Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_call_decline.xml b/core/res/res/drawable/ic_call_decline.xml
new file mode 100644
index 0000000..a5ee8f4
--- /dev/null
+++ b/core/res/res/drawable/ic_call_decline.xml
@@ -0,0 +1,36 @@
+<!--
+     Copyright (C) 2021 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M5.2834,9.3083V10.7833L4.1084,11.4916L3.1334,10.5166C3.8084,10.0416
+            4.5251,9.6333 5.2834,9.3083ZM14.75,9.325C15.4917,9.65 16.2084,10.05
+            16.875,10.525L15.925,11.475L14.75,10.7666V9.325ZM9.975,6.6666C6.8584,6.6666 3.725,7.7333
+            1.1751,9.9416C0.8084,10.2583 0.9667,10.7166 1.1501,10.9L3.2917,13.0416C3.4917,13.2333
+            3.7417,13.3333 4.0001,13.3333C4.175,13.3333 4.35,13.2833
+            4.5084,13.1916L6.4667,12.0166C6.725,11.8583 6.95,11.5583 6.95,11.1666V8.3833C7.95,8.125
+            8.975,8 10.0084,8C11.0417,8 12.075,8.1333 13.0834,8.3916V11.1416C13.0834,11.4916
+            13.2667,11.8166 13.5667,11.9916L15.525,13.1666C15.6834,13.2583 15.8584,13.3083
+            16.0334,13.3083C16.2917,13.3083 16.5417,13.2083
+            16.7334,13.0166L18.85,10.9C19.1167,10.6333 19.1167,10.1916 18.825,9.9416C16.3334,7.7833
+            13.1667,6.6666 9.975,6.6666Z"/>
+</vector>
diff --git a/core/res/res/layout/alert_dialog_button_bar_leanback.xml b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
new file mode 100644
index 0000000..ea94af6
--- /dev/null
+++ b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/buttonPanel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbarAlwaysDrawVerticalTrack="true"
+            android:scrollIndicators="top|bottom"
+            android:fillViewport="true"
+            style="?attr/buttonBarStyle">
+    <com.android.internal.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:gravity="start">
+
+        <Button
+            android:id="@+id/button1"
+            style="?attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/button2"
+            style="?attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Space
+            android:id="@+id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <Button
+            android:id="@+id/button3"
+            style="?attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
diff --git a/core/res/res/layout/alert_dialog_leanback.xml b/core/res/res/layout/alert_dialog_leanback.xml
index 848015c..091613f 100644
--- a/core/res/res/layout/alert_dialog_leanback.xml
+++ b/core/res/res/layout/alert_dialog_leanback.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2014 The Android Open Source Project
+     Copyright (C) 2021 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.
@@ -15,108 +15,70 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<com.android.internal.widget.AlertDialogLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/parentPanel"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:background="@drawable/dialog_background_material"
-    android:translationZ="@dimen/floating_window_z"
-    android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin"
-    android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin"
-    android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
-    android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin">
+    android:gravity="start|top"
+    android:orientation="vertical">
 
-    <LinearLayout android:id="@+id/topPanel"
+    <include layout="@layout/alert_dialog_title_material" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="vertical">
-        <LinearLayout android:id="@+id/title_template"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:gravity="center_vertical|start"
-            android:paddingStart="16dip"
-            android:paddingEnd="16dip"
-            android:paddingTop="16dip">
-            <ImageView android:id="@+id/icon"
-                android:layout_width="32dip"
-                android:layout_height="32dip"
-                android:layout_marginEnd="8dip"
-                android:scaleType="fitCenter"
-                android:src="@null" />
-            <TextView android:id="@+id/alertTitle"
-                style="?attr/windowTitleStyle"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:textAlignment="viewStart" />
-        </LinearLayout>
-        <!-- If the client uses a customTitle, it will be added here. -->
-    </LinearLayout>
+        android:minHeight="48dp">
 
-    <LinearLayout android:id="@+id/contentPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:minHeight="64dp">
-        <ScrollView android:id="@+id/scrollView"
+        <ScrollView
+            android:id="@+id/scrollView"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:clipToPadding="false">
-            <TextView android:id="@+id/message"
-                style="?attr/textAppearanceMedium"
+
+            <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:paddingStart="16dip"
-                android:paddingEnd="16dip"
-                android:paddingTop="16dip" />
-        </ScrollView>
-    </LinearLayout>
+                android:orientation="vertical">
 
-    <FrameLayout android:id="@+id/customPanel"
+                <Space
+                    android:id="@+id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="?attr/dialogPreferredPadding"
+                    android:paddingStart="?attr/dialogPreferredPadding"
+                    style="@style/TextAppearance.Material.Subhead" />
+
+                <Space
+                    android:id="@+id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/customPanel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:minHeight="64dp">
-        <FrameLayout android:id="@+android:id/custom"
+        android:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
     </FrameLayout>
 
-    <LinearLayout android:id="@+id/buttonPanel"
+    <include
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="@dimen/alert_dialog_button_bar_height"
-        android:orientation="vertical"
-        android:gravity="end"
-        android:padding="16dip">
-        <LinearLayout
-            style="?attr/buttonBarStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layoutDirection="locale">
-            <Button android:id="@+id/button3"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height" />
-            <Button android:id="@+id/button2"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height" />
-            <Button android:id="@+id/button1"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height" />
-        </LinearLayout>
-     </LinearLayout>
-</LinearLayout>
+        layout="@layout/alert_dialog_button_bar_leanback" />
+</com.android.internal.widget.AlertDialogLayout>
diff --git a/core/res/res/layout/notification_material_action_emphasized.xml b/core/res/res/layout/notification_material_action_emphasized.xml
index a6b7b38..cd1f1ab 100644
--- a/core/res/res/layout/notification_material_action_emphasized.xml
+++ b/core/res/res/layout/notification_material_action_emphasized.xml
@@ -22,6 +22,7 @@
     android:layout_height="match_parent"
     android:layout_marginStart="12dp"
     android:layout_weight="1"
+    android:drawablePadding="6dp"
     android:gravity="center"
     android:textColor="@color/notification_default_color"
     android:singleLine="true"
diff --git a/core/res/res/layout/notification_template_conversation_header.xml b/core/res/res/layout/notification_template_conversation_header.xml
new file mode 100644
index 0000000..b018676
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_header.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/conversation_header"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingTop="16dp"
+    >
+
+    <TextView
+        android:id="@+id/conversation_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+        android:textSize="16sp"
+        android:singleLine="true"
+        android:layout_weight="1"
+        />
+
+    <TextView
+        android:id="@+id/app_name_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:layout_gravity="center"
+        android:paddingTop="1sp"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <!-- App Name -->
+    <com.android.internal.widget.ObservableTextView
+        android:id="@+id/app_name_text"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+        android:paddingTop="1sp"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/time_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:layout_gravity="center"
+        android:paddingTop="1sp"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <DateTimeView
+        android:id="@+id/time"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:paddingTop="1sp"
+        android:showRelative="true"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ViewStub
+        android:id="@+id/chronometer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:layout="@layout/notification_template_part_chronometer"
+        android:visibility="gone"
+        />
+
+    <ImageView
+        android:id="@+id/verification_icon"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="4dp"
+        android:contentDescription="@string/notification_alerted_content_description"
+        android:paddingTop="2dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/verification_text"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:paddingTop="1sp"
+        android:showRelative="true"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ImageButton
+        android:id="@+id/feedback"
+        android:layout_width="@dimen/notification_feedback_size"
+        android:layout_height="@dimen/notification_feedback_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:background="?android:selectableItemBackgroundBorderless"
+        android:contentDescription="@string/notification_feedback_indicator"
+        android:paddingTop="2dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_feedback_indicator"
+        android:visibility="gone"
+        />
+
+    <ImageView
+        android:id="@+id/profile_badge"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="4dp"
+        android:paddingTop="2dp"
+        android:scaleType="fitCenter"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
+        />
+
+    <ImageView
+        android:id="@+id/alerted_icon"
+        android:layout_width="@dimen/notification_alerted_size"
+        android:layout_height="@dimen/notification_alerted_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="4dp"
+        android:contentDescription="@string/notification_alerted_content_description"
+        android:paddingTop="2dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
new file mode 100644
index 0000000..e9ec7ce
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/conversation_icon_container"
+    android:layout_width="@dimen/conversation_content_start"
+    android:layout_height="wrap_content"
+    android:gravity="start|top"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
+    android:importantForAccessibility="no"
+    >
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layout_gravity="top|center_horizontal"
+        >
+
+        <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
+        <com.android.internal.widget.CachingIconView
+            android:id="@+id/conversation_icon"
+            android:layout_width="@dimen/conversation_avatar_size"
+            android:layout_height="@dimen/conversation_avatar_size"
+            android:scaleType="centerCrop"
+            android:importantForAccessibility="no"
+            />
+
+        <ViewStub
+            android:layout="@layout/conversation_face_pile_layout"
+            android:layout_width="@dimen/conversation_avatar_size"
+            android:layout_height="@dimen/conversation_avatar_size"
+            android:id="@+id/conversation_face_pile"
+            />
+
+        <FrameLayout
+            android:id="@+id/conversation_icon_badge"
+            android:layout_width="@dimen/conversation_icon_size_badged"
+            android:layout_height="@dimen/conversation_icon_size_badged"
+            android:layout_marginLeft="@dimen/conversation_badge_side_margin"
+            android:layout_marginTop="@dimen/conversation_badge_side_margin"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            >
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/conversation_icon_badge_bg"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                android:src="@drawable/conversation_badge_background"
+                android:forceHasOverlappingRendering="false"
+                android:scaleType="center"
+                />
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/icon"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="4dp"
+                android:layout_gravity="center"
+                android:forceHasOverlappingRendering="false"
+                />
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/conversation_icon_badge_ring"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:src="@drawable/conversation_badge_ring"
+                android:visibility="gone"
+                android:forceHasOverlappingRendering="false"
+                android:clipToPadding="false"
+                android:scaleType="center"
+                />
+        </FrameLayout>
+    </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
new file mode 100644
index 0000000..471d874
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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
+  -->
+<com.android.internal.widget.CallLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:clipChildren="false"
+    android:tag="call"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    >
+
+    <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+    <include layout="@layout/notification_template_conversation_icon_container" />
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_action_list_height"
+        android:orientation="vertical"
+        >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="top"
+            android:orientation="horizontal"
+            >
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginStart="@dimen/conversation_content_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end"
+                android:orientation="vertical"
+                android:minHeight="68dp"
+                >
+
+                <include
+                    layout="@layout/notification_template_conversation_header"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+
+                <include layout="@layout/notification_template_text" />
+
+                <include
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_progress_bar_height"
+                    android:layout_marginTop="@dimen/notification_progress_margin_top"
+                    layout="@layout/notification_template_progress"
+                    />
+            </LinearLayout>
+
+            <!-- TODO(b/179178086): remove padding from main column when this is visible -->
+            <com.android.internal.widget.NotificationExpandButton
+                android:id="@+id/expand_button"
+                android:layout_width="@dimen/notification_header_expand_icon_size"
+                android:layout_height="@dimen/notification_header_expand_icon_size"
+                android:layout_gravity="top|end"
+                android:contentDescription="@string/expand_button_content_description_collapsed"
+                android:paddingTop="@dimen/notification_expand_button_padding_top"
+                android:scaleType="center"
+                android:visibility="gone"
+                />
+
+        </LinearLayout>
+
+        <include layout="@layout/notification_material_action_list" />
+
+    </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index f9364d5..f3aa540 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -24,82 +24,7 @@
     android:theme="@style/Theme.DeviceDefault.Notification"
     >
 
-    <FrameLayout
-        android:id="@+id/conversation_icon_container"
-        android:layout_width="@dimen/conversation_content_start"
-        android:layout_height="wrap_content"
-        android:gravity="start|top"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:paddingTop="12dp"
-        android:paddingBottom="12dp"
-        android:importantForAccessibility="no"
-    >
-
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:layout_gravity="top|center_horizontal"
-        >
-
-            <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
-            <com.android.internal.widget.CachingIconView
-                android:id="@+id/conversation_icon"
-                android:layout_width="@dimen/conversation_avatar_size"
-                android:layout_height="@dimen/conversation_avatar_size"
-                android:scaleType="centerCrop"
-                android:importantForAccessibility="no"
-            />
-
-            <ViewStub
-                android:layout="@layout/conversation_face_pile_layout"
-                android:layout_width="@dimen/conversation_avatar_size"
-                android:layout_height="@dimen/conversation_avatar_size"
-                android:id="@+id/conversation_face_pile"
-                />
-
-            <FrameLayout
-                android:id="@+id/conversation_icon_badge"
-                android:layout_width="@dimen/conversation_icon_size_badged"
-                android:layout_height="@dimen/conversation_icon_size_badged"
-                android:layout_marginLeft="@dimen/conversation_badge_side_margin"
-                android:layout_marginTop="@dimen/conversation_badge_side_margin"
-                android:clipChildren="false"
-                android:clipToPadding="false"
-            >
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/conversation_icon_badge_bg"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_gravity="center"
-                    android:src="@drawable/conversation_badge_background"
-                    android:forceHasOverlappingRendering="false"
-                    android:scaleType="center"
-                />
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/icon"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_margin="4dp"
-                    android:layout_gravity="center"
-                    android:forceHasOverlappingRendering="false"
-                />
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/conversation_icon_badge_ring"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:src="@drawable/conversation_badge_ring"
-                    android:visibility="gone"
-                    android:forceHasOverlappingRendering="false"
-                    android:clipToPadding="false"
-                    android:scaleType="center"
-                />
-            </FrameLayout>
-        </FrameLayout>
-    </FrameLayout>
+    <include layout="@layout/notification_template_conversation_icon_container" />
 
     <!-- Wraps entire "expandable" notification -->
     <com.android.internal.widget.RemeasuringLinearLayout
@@ -132,161 +57,14 @@
 
                 <!-- Use layout_marginStart instead of paddingStart to work around strange
                      measurement behavior on lower display densities. -->
-                <LinearLayout
-                    android:id="@+id/conversation_header"
+                <include
+                    layout="@layout/notification_template_conversation_header"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:orientation="horizontal"
-                    android:paddingTop="16dp"
                     android:layout_marginBottom="2dp"
                     android:layout_marginStart="@dimen/conversation_content_start"
-                >
-                    <TextView
-                        android:id="@+id/conversation_text"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
-                        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
-                        android:textSize="16sp"
-                        android:singleLine="true"
-                        android:layout_weight="1"
-                        />
-
-                    <TextView
-                        android:id="@+id/app_name_divider"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textAppearance="?attr/notificationHeaderTextAppearance"
-                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
-                        android:text="@string/notification_header_divider_symbol"
-                        android:layout_gravity="center"
-                        android:paddingTop="1sp"
-                        android:singleLine="true"
-                        android:visibility="gone"
                     />
 
-                    <!-- App Name -->
-                    <com.android.internal.widget.ObservableTextView
-                        android:id="@+id/app_name_text"
-                        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_gravity="center"
-                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
-                        android:paddingTop="1sp"
-                        android:singleLine="true"
-                        android:visibility="gone"
-                    />
-
-                    <TextView
-                        android:id="@+id/time_divider"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textAppearance="?attr/notificationHeaderTextAppearance"
-                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
-                        android:text="@string/notification_header_divider_symbol"
-                        android:layout_gravity="center"
-                        android:paddingTop="1sp"
-                        android:singleLine="true"
-                        android:visibility="gone"
-                    />
-
-                    <DateTimeView
-                        android:id="@+id/time"
-                        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_gravity="center"
-                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-                        android:paddingTop="1sp"
-                        android:showRelative="true"
-                        android:singleLine="true"
-                        android:visibility="gone"
-                    />
-
-                    <ImageButton
-                        android:id="@+id/feedback"
-                        android:layout_width="@dimen/notification_feedback_size"
-                        android:layout_height="@dimen/notification_feedback_size"
-                        android:layout_gravity="center"
-                        android:layout_marginStart="@dimen/notification_header_separating_margin"
-                        android:background="?android:selectableItemBackgroundBorderless"
-                        android:contentDescription="@string/notification_feedback_indicator"
-                        android:paddingTop="2dp"
-                        android:scaleType="fitCenter"
-                        android:src="@drawable/ic_feedback_indicator"
-                        android:visibility="gone"
-                        />
-
-                    <ImageView
-                        android:id="@+id/profile_badge"
-                        android:layout_width="@dimen/notification_badge_size"
-                        android:layout_height="@dimen/notification_badge_size"
-                        android:layout_gravity="center"
-                        android:layout_marginStart="4dp"
-                        android:paddingTop="2dp"
-                        android:scaleType="fitCenter"
-                        android:visibility="gone"
-                        android:contentDescription="@string/notification_work_profile_content_description"
-                        />
-
-                    <ImageView
-                        android:id="@+id/alerted_icon"
-                        android:layout_width="@dimen/notification_alerted_size"
-                        android:layout_height="@dimen/notification_alerted_size"
-                        android:layout_gravity="center"
-                        android:layout_marginStart="4dp"
-                        android:contentDescription="@string/notification_alerted_content_description"
-                        android:paddingTop="2dp"
-                        android:scaleType="fitCenter"
-                        android:src="@drawable/ic_notifications_alerted"
-                        android:visibility="gone"
-                        />
-
-                    <LinearLayout
-                        android:id="@+id/app_ops"
-                        android:layout_height="wrap_content"
-                        android:layout_width="wrap_content"
-                        android:paddingTop="3dp"
-                        android:layout_marginStart="2dp"
-                        android:background="?android:selectableItemBackgroundBorderless"
-                        android:orientation="horizontal" >
-                        <ImageView
-                            android:layout_marginStart="4dp"
-                            android:id="@+id/camera"
-                            android:layout_width="?attr/notificationHeaderIconSize"
-                            android:layout_height="?attr/notificationHeaderIconSize"
-                            android:src="@drawable/ic_camera"
-                            android:visibility="gone"
-                            android:focusable="false"
-                            android:contentDescription="@string/notification_appops_camera_active"
-                            />
-                        <ImageView
-                            android:id="@+id/mic"
-                            android:layout_width="?attr/notificationHeaderIconSize"
-                            android:layout_height="?attr/notificationHeaderIconSize"
-                            android:src="@drawable/ic_mic"
-                            android:layout_marginStart="4dp"
-                            android:visibility="gone"
-                            android:focusable="false"
-                            android:contentDescription="@string/notification_appops_microphone_active"
-                            />
-                        <ImageView
-                            android:id="@+id/overlay"
-                            android:layout_width="?attr/notificationHeaderIconSize"
-                            android:layout_height="?attr/notificationHeaderIconSize"
-                            android:src="@drawable/ic_alert_window_layer"
-                            android:layout_marginStart="4dp"
-                            android:visibility="gone"
-                            android:focusable="false"
-                            android:contentDescription="@string/notification_appops_overlay_active"
-                            />
-                    </LinearLayout>
-                </LinearLayout>
-
                 <!-- Messages -->
                 <com.android.internal.widget.MessagingLinearLayout
                     android:id="@+id/notification_messaging"
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 7cda03f..7656dd5 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -26,7 +26,7 @@
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:singleLine="true"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:visibility="?attr/notificationHeaderAppNameVisibility"
         />
 
@@ -34,7 +34,7 @@
         android:id="@+id/header_text_secondary_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
@@ -45,7 +45,7 @@
         android:id="@+id/header_text_secondary"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:visibility="gone"
@@ -56,7 +56,7 @@
         android:id="@+id/header_text_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
@@ -67,7 +67,7 @@
         android:id="@+id/header_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:visibility="gone"
@@ -78,7 +78,7 @@
         android:id="@+id/time_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
diff --git a/core/res/res/values-television/styles_device_defaults.xml b/core/res/res/values-television/styles_device_defaults.xml
new file mode 100644
index 0000000..ef55fc6
--- /dev/null
+++ b/core/res/res/values-television/styles_device_defaults.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog"
+           parent="Widget.Leanback.Button.ButtonBar" />
+</resources>
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e567c3d..aeb4fc468 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1140,6 +1140,15 @@
         <!-- The color applied to the edge effect on scrolling containers. -->
         <attr name="colorEdgeEffect" format="color" />
 
+        <!-- The type of the edge effect. The default is glow. -->
+        <attr name="edgeEffectType">
+            <!-- Use a colored glow at the edge. -->
+            <enum name="glow" value="0" />
+
+            <!-- Stretch the content. -->
+            <enum name="stretch" value="1" />
+        </attr>
+
         <!-- =================== -->
         <!-- Lighting properties -->
         <!-- =================== -->
@@ -3251,6 +3260,16 @@
              a value of 'true' will not override any 'false' value in its parent chain nor will
              it prevent any 'false' in any of its children. -->
         <attr name="forceDarkAllowed" format="boolean" />
+
+        <!-- <p>Whether the View's Outline should be used to clip the contents of the View.
+             <p>Only a single non-rectangular clip can be applied on a View at any time. Circular
+             clips from a
+             {@link android.view.ViewAnimationUtils#createCircularReveal(View, int, int, float,
+             float)} circular reveal animation take priority over Outline clipping, and child
+             Outline clipping takes priority over Outline clipping done by a parent.
+             <p>Note that this flag will only be respected if the View's Outline returns true from
+             {@link android.graphics.Outline#canClip()}. -->
+        <attr name="clipToOutline" format="boolean" />
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -9040,6 +9059,7 @@
     <!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->
     <declare-styleable name="EdgeEffect">
         <attr name="colorEdgeEffect" />
+        <attr name="edgeEffectType" />
     </declare-styleable>
 
     <!-- Use <code>tv-input</code> as the root tag of the XML resource that describes a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b4e580a..0ae6a76 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -311,6 +311,10 @@
         <flag name="recents" value="0x2000000" />
         <!-- Additional flag from base permission type: this permission is managed by role. -->
         <flag name="role" value="0x4000000" />
+        <!-- Additional flag from base permission type: this permission can also be granted if the
+             requesting application is signed by, or has in its signing lineage, any of the
+             certificate digests declared in {@link android.R.attr#knownCerts}. -->
+        <flag name="knownSigner" value="0x8000000" />
     </attr>
 
     <!-- Flags indicating more context for a permission group. -->
@@ -364,6 +368,15 @@
          {@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
     <attr name="permissionGroup" format="string" />
 
+    <!-- A reference to an array resource containing the signing certificate digests to be granted
+         this permission when using the {@code knownSigner} protection flag. The digest should
+         be computed over the DER encoding of the trusted certificate using the SHA-256 digest
+         algorithm.
+         <p>
+         If only a single signer is declared this can also be a string resource, or the digest
+         can be declared inline as the value for this attribute. -->
+    <attr name="knownCerts" format="reference|string" />
+
     <!-- Specify the name of a user ID that will be shared between multiple
          packages.  By default, each package gets its own unique user-id.
          By setting this value on two or more packages, each of these packages
@@ -1935,6 +1948,7 @@
         <attr name="request" />
         <attr name="protectionLevel" />
         <attr name="permissionFlags" />
+        <attr name="knownCerts" />
     </declare-styleable>
 
     <!-- The <code>permission-group</code> tag declares a logical grouping of
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 20a5d37..5546621 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -153,6 +153,11 @@
 
     <color name="notification_action_list_background_color">@null</color>
 
+    <!-- The color of the Decline and Hang Up actions on a CallStyle notification -->
+    <color name="call_notification_decline_color">#d93025</color>
+    <!-- The color of the Answer action on a CallStyle notification -->
+    <color name="call_notification_answer_color">#1e8e3e</color>
+
     <!-- Keyguard colors -->
     <color name="keyguard_avatar_frame_color">#ffffffff</color>
     <color name="keyguard_avatar_frame_shadow_color">#80000000</color>
diff --git a/core/res/res/values/colors_leanback.xml b/core/res/res/values/colors_leanback.xml
index e52a861e..df0be03 100644
--- a/core/res/res/values/colors_leanback.xml
+++ b/core/res/res/values/colors_leanback.xml
@@ -26,4 +26,9 @@
     <color name="secondary_text_leanback_light">#99222222</color>
 
     <color name="primary_text_leanback_formwizard_default_dark">#ffeeeeee</color>
+
+    <color name="btn_leanback_focused">#E8EAED</color>
+    <color name="btn_leanback_unfocused">#1AFFFFFF</color>
+    <color name="btn_text_leanback_focused">#0E0E0E</color>
+    <color name="btn_text_leanback_unfocused">#E8EAED</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8c5f454..09ca12a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,8 +1946,6 @@
     <string name="config_systemAutomotiveProjection" translatable="false"></string>
     <!-- The name of the package that will hold the system cluster service role. -->
     <string name="config_systemAutomotiveCluster" translatable="false"></string>
-    <!-- The name of the package that will hold the system video call role. -->
-    <string name="config_systemVideoCall" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -4712,4 +4710,10 @@
 
     <!-- Whether to select voice/data/sms preference without user confirmation -->
     <bool name="config_voice_data_sms_auto_fallback">false</bool>
+
+    <!-- Whether to enable the one-handed keyguard on the lock screen for wide-screen devices. -->
+    <bool name="config_enableOneHandedKeyguard">false</bool>
+
+    <!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot -->
+    <bool name="config_allow_pin_storage_for_unattended_reboot">true</bool>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cb16af5..debdab0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -236,9 +236,20 @@
          value is calculated in ConversationLayout#updateActionListPadding() -->
     <dimen name="notification_actions_padding_start">36dp</dimen>
 
+    <!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
+         notification actions.
+         this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
+    <dimen name="call_notification_collapsible_indent">64dp</dimen>
+
     <!-- The size of icons for visual actions in the notification_material_action_list -->
     <dimen name="notification_actions_icon_size">48dp</dimen>
 
+    <!-- The size of icons for visual actions in the notification_material_action_list -->
+    <dimen name="notification_actions_icon_drawable_size">20dp</dimen>
+
+    <!-- The corner radius if the emphasized action buttons in a notification -->
+    <dimen name="notification_action_button_radius">8dp</dimen>
+
     <!-- Size of the stroke with for the emphasized notification button style -->
     <dimen name="emphasized_button_stroke_width">1dp</dimen>
 
@@ -383,10 +394,6 @@
     <dimen name="alert_dialog_button_bar_width">64dp</dimen>
     <!-- Dialog button bar height -->
     <dimen name="alert_dialog_button_bar_height">48dip</dimen>
-    <!-- Leanback dialog vertical margin -->
-    <dimen name="leanback_alert_dialog_vertical_margin">27dip</dimen>
-    <!-- Leanback dialog horizontal margin -->
-    <dimen name="leanback_alert_dialog_horizontal_margin">54dip</dimen>
 
     <!-- Default height of an action bar. -->
     <dimen name="action_bar_default_height">48dip</dimen>
diff --git a/core/res/res/values/dimens_leanback.xml b/core/res/res/values/dimens_leanback.xml
index c824a2a..3ab2196 100644
--- a/core/res/res/values/dimens_leanback.xml
+++ b/core/res/res/values/dimens_leanback.xml
@@ -83,4 +83,10 @@
     <dimen name="leanback_setup_translation_backward_out_content_end_v4">@integer/leanback_setup_translation_content_cliff_v4</dimen>
     <integer name="leanback_setup_translation_backward_out_content_delay">0</integer>
     <integer name="leanback_setup_translation_backward_out_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+    <!-- Button dimens -->
+    <dimen name="leanback_button_height">42dp</dimen>
+    <dimen name="leanback_button_radius">55dp</dimen>
+    <dimen name="leanback_button_padding_horizontal">22dp</dimen>
+    <dimen name="leanback_button_padding_vertical">11dp</dimen>
 </resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index a4c7293..ab4e0f3 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -102,6 +102,10 @@
   <item type="id" name="selection_end_handle" />
   <item type="id" name="insertion_handle" />
   <item type="id" name="floating_toolbar_menu_item_image_button" />
+  <item type="id" name="camera" />
+  <item type="id" name="mic" />
+  <item type="id" name="overlay" />
+  <item type="id" name="app_ops" />
 
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_ON_SCREEN}. -->
   <item type="id" name="accessibilityActionShowOnScreen" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 97ec0f4..068987e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3062,6 +3062,9 @@
     <!-- @hide @SystemApi -->
     <public name="hotwordDetectionService" />
     <public name="previewLayout" />
+    <public name="clipToOutline" />
+    <public name="edgeEffectType" />
+    <public name="knownCerts" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
@@ -3116,8 +3119,6 @@
     <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveCluster" />
     <!-- @hide @SystemApi @TestApi -->
-    <public name="config_systemVideoCall" />
-    <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveProjection" />
   </public-group>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9b5f670..af5e406 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5039,6 +5039,18 @@
     <!-- Tempalate for Notification.MessagingStyle to join a conversation name with the name of the sender of a message, to make a notification title [CHAR LIMIT=NONE] -->
     <string name="notification_messaging_title_template"><xliff:g id="conversation_title" example="Tasty Treat Team">%1$s</xliff:g>: <xliff:g id="sender_name" example="Adrian Baker">%2$s</xliff:g></string>
 
+    <!-- Action text to be displayed for the "answer" action of an incoming call [CHAR LIMIT=13] -->
+    <string name="call_notification_answer_action">Answer</string>
+    <!-- Action text to be displayed for the "decline" action of an incoming call [CHAR LIMIT=13] -->
+    <string name="call_notification_decline_action">Decline</string>
+    <!-- Action text to be displayed for the "hang up" action of an ongoing call [CHAR LIMIT=13] -->
+    <string name="call_notification_hang_up_action">Hang Up</string>
+    <!-- Default notification text to be displayed in incoming call notifications [CHAR LIMIT=40] -->
+    <string name="call_notification_incoming_text">Incoming call</string>
+    <!-- Default notification text to be displayed in ongoing call notifications [CHAR LIMIT=40] -->
+    <string name="call_notification_ongoing_text">Ongoing call</string>
+    <!-- Default notification text to be displayed in screening call notifications [CHAR LIMIT=40] -->
+    <string name="call_notification_screening_text">Screening an incoming call</string>
 
     <!-- Label describing the number of selected items [CHAR LIMIT=48] -->
     <plurals name="selected_count">
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index aaeaadd..7eaf36d 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -16,11 +16,34 @@
 <resources>
     <style name="AlertDialog.Leanback" parent="AlertDialog.Material">
         <item name="buttonPanelSideLayout">@android:layout/alert_dialog_leanback_button_panel_side</item>
+        <item name="layout">@android:layout/alert_dialog_leanback</item>
     </style>
 
     <style name="AlertDialog.Leanback.Light">
     </style>
 
+    <style name="Widget.Leanback.Button" parent="Widget.Material.Button">
+        <item name="android:background">@drawable/btn_leanback</item>
+        <item name="android:textColor">@color/btn_leanback_text_color</item>
+        <item name="android:layout_height">@dimen/leanback_button_height</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:paddingHorizontal">@dimen/leanback_button_padding_horizontal</item>
+        <item name="android:paddingVertical">@dimen/leanback_button_padding_vertical</item>
+    </style>
+
+    <style name="Widget.Leanback.Button.ButtonBar" parent="Widget.Leanback.Button">
+        <item name="android:layout_marginStart">10dp</item>
+    </style>
+
+    <style name="Widget.Leanback.Button.ButtonBarGravityStart" parent="Widget.Leanback.Button">
+        <item name="android:layout_marginEnd">10dp</item>
+    </style>
+
+    <style name="Widget.Leanback.ButtonBar" parent="Widget.Material.ButtonBar">
+        <item name="android:padding">?android:attr/dialogPreferredPadding</item>
+    </style>
+
     <style name="Widget.Leanback.TimePicker" parent="Widget.Material.TimePicker">
         <item name="timePickerMode">spinner</item>
     </style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c41b78e..4109d4c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2647,6 +2647,7 @@
   <java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" />
   <java-symbol type="bool" name="config_overrideRemoteViewsActivityTransition" />
   <java-symbol type="attr" name="colorProgressBackgroundNormal" />
+  <java-symbol type="bool" name="config_allow_pin_storage_for_unattended_reboot" />
 
   <java-symbol type="layout" name="simple_account_item" />
   <java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" />
@@ -3088,8 +3089,26 @@
 
   <!-- TV Remote Service package -->
   <java-symbol type="string" name="config_tvRemoteServicePackage" />
+
+  <!-- Notifications: MessagingStyle -->
   <java-symbol type="string" name="notification_messaging_title_template" />
 
+  <!-- Notifications: CallStyle -->
+  <java-symbol type="layout" name="notification_template_material_call" />
+  <java-symbol type="string" name="call_notification_answer_action" />
+  <java-symbol type="string" name="call_notification_decline_action" />
+  <java-symbol type="string" name="call_notification_hang_up_action" />
+  <java-symbol type="string" name="call_notification_incoming_text" />
+  <java-symbol type="string" name="call_notification_ongoing_text" />
+  <java-symbol type="string" name="call_notification_screening_text" />
+  <java-symbol type="color" name="call_notification_decline_color"/>
+  <java-symbol type="color" name="call_notification_answer_color"/>
+  <java-symbol type="dimen" name="call_notification_collapsible_indent"/>
+  <java-symbol type="drawable" name="ic_call_answer" />
+  <java-symbol type="drawable" name="ic_call_decline" />
+  <java-symbol type="id" name="verification_icon" />
+  <java-symbol type="id" name="verification_text" />
+
   <!-- Notification handler / dashboard package -->
   <java-symbol type="string" name="config_notificationHandlerPackage" />
 
@@ -3415,6 +3434,7 @@
   <java-symbol type="dimen" name="notification_media_image_max_width"/>
   <java-symbol type="dimen" name="notification_media_image_max_height"/>
   <java-symbol type="dimen" name="notification_right_icon_size"/>
+  <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
 
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 9dca912..efe5826 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -22,6 +22,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+      <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Light.Dialog" parent="Theme.Material.Light.BaseDialog">
@@ -32,6 +34,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Settings.Dialog" parent="Theme.Material.Settings.BaseDialog">
@@ -42,6 +46,8 @@
         <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
         <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
         <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+        <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert">
@@ -52,6 +58,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+      <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert">
@@ -62,6 +70,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.BaseAlert">
@@ -72,6 +82,8 @@
         <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
         <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
         <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+        <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Dialog.AppError" parent="Theme.Leanback.Dialog">
diff --git a/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java b/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
index cfcfcc8..ece37f8 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
@@ -201,6 +201,18 @@
         assertThat(BundleUtil.deepHashCode(b1)).isNotEqualTo(BundleUtil.deepHashCode(b2));
     }
 
+    @Test
+    public void testDeepHashCode_differentKeys() {
+        Bundle[] inputs = new Bundle[2];
+        for (int i = 0; i < 2; i++) {
+            Bundle b = new Bundle();
+            b.putString("key" + i, "value");
+            inputs[i] = b;
+        }
+        assertThat(BundleUtil.deepHashCode(inputs[0]))
+                .isNotEqualTo(BundleUtil.deepHashCode(inputs[1]));
+    }
+
     private static Bundle createThoroughBundle() {
         Bundle toy1 = new Bundle();
         toy1.putString("a", "a");
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index bffd1e4..49b720c 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -29,6 +29,7 @@
 
 import android.content.pm.PackageParser.SigningDetails;
 import android.util.ArraySet;
+import android.util.PackageUtils;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -817,6 +818,124 @@
         assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
     }
 
+    @Test
+    public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception {
+        // The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails
+        // is currently signed, or has previously been signed, by any of the certificate digests
+        // in the provided Set. This test verifies if a null Set is provided then false is returned.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(null));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception {
+        // If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN
+        // instance of the SigningDetails then false is returned.
+        SigningDetails details = SigningDetails.UNKNOWN;
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception {
+        // If the single signer of an app is in the provided digest Set then
+        // the method should return true.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception {
+        // If the single signer of an app is not in the provided digest Set then
+        // the method should return false.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception {
+        // If an app is signed by multiple signers and all of the signers are in
+        // the digest Set then the method should return true.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse()
+            throws Exception {
+        // If an app is signed by multiple signers then all signers must be in the digest Set; if
+        // only a subset of the signers are in the Set then the method should return false.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse()
+            throws Exception {
+        // If an app is signed by multiple signers and the Set size is smaller than the number of
+        // signers then the method should immediately return false since there's no way for the
+        // requirement of all signers in the Set to be met.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception {
+        // If an app has a rotated signing key and a previous key in the lineage is in the digest
+        // Set then this method should return true.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception {
+        // If an app has a rotated signing key, but neither the current key nor any of the signers
+        // in the lineage are in the digest set then the method should return false.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue()
+            throws Exception {
+        // If an app has multiple signers in the lineage only one of those signers must be in the
+        // Set for this method to return true. This test verifies if the last signer in the lineage
+        // is in the set then the method returns true.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE,
+                THIRD_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse()
+            throws Exception {
+        // Under some instances an app with only a single signer can have a null lineage; this
+        // test verifies that null lineage does not result in a NullPointerException and instead the
+        // method returns false if the single signer is not in the Set.
+        SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
     private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
         int[] capabilities = new int[signers.length];
         for (int i = 0; i < capabilities.length; i++) {
@@ -853,12 +972,21 @@
         // If there are multiple signers then the pastSigningCertificates should be set to null, but
         // if there is only a single signer both the current signer and the past signers should be
         // set to that one signer.
-        if (signers.length > 1) {
+        if (signers.length > 1 || useNullPastSigners) {
             return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
         }
         return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
     }
 
+    private Set<String> createDigestSet(String... signers) {
+        Set<String> digests = new ArraySet<>();
+        for (String signer : signers) {
+            String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray());
+            digests.add(digest);
+        }
+        return digests;
+    }
+
     private void assertSigningDetailsContainsLineage(SigningDetails details,
             String... pastSigners) {
         // This method should only be invoked for results that contain a single signer.
diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS
new file mode 100644
index 0000000..581da71
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/provider/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 64906bb..e16d448 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -100,7 +100,7 @@
         final List<UsageStats> slices = new ArrayList<>();
         slices.add(packageStats);
         ParceledListSlice<UsageStats> stats = new ParceledListSlice<>(slices);
-        when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString()))
+        when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString(), anyInt()))
                 .thenReturn(stats);
         Answer<Void> answer = new Answer<Void>() {
             @Override
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index d2107ea..08205b4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -218,7 +218,7 @@
         sippers.add(sipperBg);
         sippers.add(sipperFg);
 
-        spc.smearScreenBatterySipper(sippers, mScreenBatterySipper);
+        spc.smearScreenBatterySipper(sippers, mScreenBatterySipper, 0);
 
         assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0);
         assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0);
@@ -253,7 +253,7 @@
         doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
                 anyLong(), anyInt());
 
-        final long time = spc.getProcessForegroundTimeMs(mUid);
+        final long time = spc.getProcessForegroundTimeMs(mUid, 1000);
 
         assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
     }
@@ -296,7 +296,7 @@
         doReturn(uidCode).when(sipper).getUid();
         if (!isUidNull) {
             final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
-            doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid));
+            doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid), anyLong());
             doReturn(uidCode).when(uid).getUid();
             sipper.uidObj = uid;
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 97c07ea..4c52848 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -24,6 +24,7 @@
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
 import android.os.WorkSource;
+import android.util.SparseLongArray;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@
         checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
     }
 
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        final int uid1 = 11500;
+        final int uid2 = 11501;
+
+        // Initially, all custom buckets report energy of 0.
+        checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
+    }
+
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        final int bucketA = 0; // Custom bucket 0
+        final int bucketB = 1; // Custom bucket 1
+
+        long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
+        long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)
+
+        final int uid1 = 10500;
+        long blame1A = 0; // Blame for uid1 in bucketA
+        long blame1B = 0; // Blame for uid1 in bucketB
+
+        final int uid2 = 10501;
+        long blame2A = 0; // Blame for uid2 in bucketA
+        long blame2B = 0; // Blame for uid2 in bucketB
+
+        final SparseLongArray newEnergiesA = new SparseLongArray(2);
+        final SparseLongArray newEnergiesB = new SparseLongArray(2);
+
+
+        // ----- Case A: battery off (so blame does not increase)
+        bi.setOnBatteryInternal(false);
+
+        newEnergiesA.put(uid1, 20_000);
+        // Implicit newEnergiesA.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);
+
+        newEnergiesB.put(uid1, 60_000);
+        // Implicit newEnergiesB.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);
+
+        checkCustomMeasuredEnergy(
+                "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case B: battery on
+        bi.setOnBatteryInternal(true);
+
+        newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
+        // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
+        totalBlameA += 310_000;
+
+        newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
+        newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
+        totalBlameB += 790_000;
+
+        checkCustomMeasuredEnergy(
+                "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case C: battery still on
+        newEnergiesA.delete(uid1); blame1A += 0;
+        newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
+        totalBlameA += 560_000;
+
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
+        totalBlameB += 10_000;
+
+        checkCustomMeasuredEnergy(
+                "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case D: battery still on
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
+        totalBlameB += 15_000;
+        checkCustomMeasuredEnergy(
+                "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -610,4 +700,29 @@
         assertEquals("Wrong doze for Case " + caseName, globalDoze,
                 bi.getScreenDozeEnergy());
     }
+
+    private void checkCustomMeasuredEnergy(String caseName,
+            long totalBlameA, long totalBlameB,
+            int uid1, long blame1A, long blame1B,
+            int uid2, long blame2A, long blame2B,
+            MockBatteryStatsImpl bi) {
+
+        assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+                bi.getCustomMeasuredEnergyMicroJoules(0));
+
+        assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+                bi.getCustomMeasuredEnergyMicroJoules(1));
+
+        assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A,
+                bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(0));
+
+        assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B,
+                bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(1));
+
+        assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A,
+                bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(0));
+
+        assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B,
+                bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(1));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 143e07a..0fac4f7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -45,6 +45,7 @@
         BluetoothPowerCalculatorTest.class,
         BstatsCpuTimesValidationTest.class,
         CameraPowerCalculatorTest.class,
+        CpuPowerCalculatorTest.class,
         FlashlightPowerCalculatorTest.class,
         GnssPowerCalculatorTest.class,
         IdlePowerCalculatorTest.class,
@@ -65,6 +66,7 @@
         ScreenPowerCalculatorTest.class,
         SensorPowerCalculatorTest.class,
         SystemServicePowerCalculatorTest.class,
+        UserPowerCalculatorTest.class,
         VideoPowerCalculatorTest.class,
 
         com.android.internal.power.MeasuredEnergyStatsTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 2c71287..0ddc4c0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -28,6 +28,7 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
+import android.os.UserBatteryConsumer;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -76,6 +77,26 @@
         return this;
     }
 
+    public BatteryUsageStatsRule setNumCpuClusters(int number) {
+        when(mPowerProfile.getNumCpuClusters()).thenReturn(number);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setNumSpeedStepsInCpuCluster(int cluster, int speeds) {
+        when(mPowerProfile.getNumSpeedStepsInCpuCluster(cluster)).thenReturn(speeds);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setAveragePowerForCpuCluster(int cluster, double value) {
+        when(mPowerProfile.getAveragePowerForCpuCluster(cluster)).thenReturn(value);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setAveragePowerForCpuCore(int cluster, int step, double value) {
+        when(mPowerProfile.getAveragePowerForCpuCore(cluster, step)).thenReturn(value);
+        return this;
+    }
+
     public void setNetworkStats(NetworkStats networkStats) {
         mBatteryStats.setNetworkStats(networkStats);
     }
@@ -113,15 +134,21 @@
         mMockClocks.uptime = uptimeUs;
     }
 
-    void apply(PowerCalculator calculator) {
+    void apply(PowerCalculator... calculators) {
+        apply(BatteryUsageStatsQuery.DEFAULT, calculators);
+    }
+
+    void apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
         }
 
-        calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
-                BatteryUsageStatsQuery.DEFAULT, null);
+        for (PowerCalculator calculator : calculators) {
+            calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
+                    query);
+        }
 
         mBatteryUsageStats = builder.build();
     }
@@ -144,4 +171,13 @@
         }
         return null;
     }
+
+    public UserBatteryConsumer getUserBatteryConsumer(int userId) {
+        for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) {
+            if (ubc.getUserId() == userId) {
+                return ubc;
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
new file mode 100644
index 0000000..9cf0d37
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CpuPowerCalculatorTest {
+    private static final double PRECISION = 0.00001;
+
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+            .setNumCpuClusters(2)
+            .setNumSpeedStepsInCpuCluster(0, 2)
+            .setNumSpeedStepsInCpuCluster(1, 2)
+            .setAveragePowerForCpuCluster(0, 360)
+            .setAveragePowerForCpuCluster(1, 480)
+            .setAveragePowerForCpuCore(0, 0, 300)
+            .setAveragePowerForCpuCore(0, 1, 400)
+            .setAveragePowerForCpuCore(1, 0, 500)
+            .setAveragePowerForCpuCore(1, 1, 600);
+
+    private final KernelCpuSpeedReader[] mMockKernelCpuSpeedReaders = new KernelCpuSpeedReader[]{
+            mock(KernelCpuSpeedReader.class),
+            mock(KernelCpuSpeedReader.class),
+    };
+
+    @Mock
+    private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader mMockKernelCpuUidClusterTimeReader;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mMockKernelCpuUidUserSysTimeReader;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader mMockKerneCpuUidActiveTimeReader;
+    @Mock
+    private SystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mStatsRule.getBatteryStats()
+                .setUserInfoProvider(mMockUserInfoProvider)
+                .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
+                .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
+                .setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader)
+                .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
+                .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
+                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
+    }
+
+    @Test
+    public void testTimerBasedModel() {
+        when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+        when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
+        when(mMockKernelCpuSpeedReaders[1].readDelta()).thenReturn(new long[]{3000, 4000});
+
+        when(mMockCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(false);
+
+        // User/System CPU time
+        doAnswer(invocation -> {
+            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+            // User/system time in microseconds
+            callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000});
+            callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000});
+            return null;
+        }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(any());
+
+        // Active CPU time
+        doAnswer(invocation -> {
+            final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(0);
+            callback.onUidCpuTime(APP_UID1, 1111L);
+            callback.onUidCpuTime(APP_UID2, 3333L);
+            return null;
+        }).when(mMockKerneCpuUidActiveTimeReader).readDelta(any());
+
+        // Per-cluster CPU time
+        doAnswer(invocation -> {
+            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+            callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222});
+            callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444});
+            return null;
+        }).when(mMockKernelCpuUidClusterTimeReader).readDelta(any());
+
+        mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true);
+
+        mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234);
+        mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);
+
+        CpuPowerCalculator calculator =
+                new CpuPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+        assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+                .isEqualTo(3333);
+        assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(1.092233);
+        assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
+
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+                .isEqualTo(7777);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(2.672322);
+        assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index fdfc7ac..d50bb05 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -232,7 +232,7 @@
         assertThat(entry3.handlerClassName).isEqualTo(
                 "com.android.internal.os.LooperStatsTest$TestHandlerSecond");
         assertThat(entry3.messageName).startsWith(
-                "com.android.internal.os.-$$Lambda$LooperStatsTest$");
+                "com.android.internal.os.LooperStatsTest-$$ExternalSyntheticLambda");
         assertThat(entry3.messageCount).isEqualTo(1);
         assertThat(entry3.recordedMessageCount).isEqualTo(1);
         assertThat(entry3.exceptionCount).isEqualTo(0);
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index e43caa3..717fac0 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -18,8 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.ActivityManager;
 import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
 import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -33,20 +37,37 @@
 @SmallTest
 public class ScreenPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43;
+    private static final long MINUTE_IN_MS = 60 * 1000;
+    private static final long MINUTE_IN_US = 60 * 1000 * 1000;
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_SCREEN_ON, 360.0)
-            .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 3600.0);
+            .setAveragePower(PowerProfile.POWER_SCREEN_ON, 36.0)
+            .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 48.0);
 
     @Test
-    public void testTimerBasedModel() {
-        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+    public void testEnergyBasedModel() {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        stats.noteScreenStateLocked(Display.STATE_ON, 1000, 1000, 1000);
-        stats.noteScreenBrightnessLocked(100, 1000, 1000);
-        stats.noteScreenBrightnessLocked(200, 2000, 2000);
-        stats.noteScreenStateLocked(Display.STATE_OFF, 3000, 3000, 3000);
+        batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+        batteryStats.updateDisplayEnergyLocked(0, Display.STATE_ON, 2 * MINUTE_IN_MS);
+
+        setFgState(APP_UID1, true, 2 * MINUTE_IN_MS, 2 * MINUTE_IN_MS);
+        setFgState(APP_UID1, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+        setFgState(APP_UID2, true, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+
+        batteryStats.updateDisplayEnergyLocked(300_000_000, Display.STATE_ON,
+                60 * MINUTE_IN_MS);
+
+        batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+        batteryStats.updateDisplayEnergyLocked(100_000_000, Display.STATE_DOZE,
+                120 * MINUTE_IN_MS);
+
+        mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
 
         ScreenPowerCalculator calculator =
                 new ScreenPowerCalculator(mStatsRule.getPowerProfile());
@@ -56,8 +77,79 @@
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
         assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
-                .isEqualTo(2000);
+                .isEqualTo(80 * MINUTE_IN_MS);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
-                .isWithin(PRECISION).of(1.2);
+                .isWithin(PRECISION).of(30.03003);
+
+        UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+        assertThat(uid1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(18 * MINUTE_IN_MS);
+        assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(8.44594);
+
+        UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uid2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(90 * MINUTE_IN_MS);
+        assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(21.58408);
+    }
+
+    @Test
+    public void testPowerProfileBasedModel() {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+
+        setFgState(APP_UID1, true, 2 * MINUTE_IN_MS, 2 * MINUTE_IN_MS);
+
+        batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+        batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+        setFgState(APP_UID1, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+        setFgState(APP_UID2, true, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+
+        batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+        mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+
+        ScreenPowerCalculator calculator =
+                new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+                calculator);
+
+        SystemBatteryConsumer consumer =
+                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+                .isEqualTo(80 * MINUTE_IN_MS);
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+                .isWithin(PRECISION).of(88.4);
+
+        UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+        assertThat(uid1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(18 * MINUTE_IN_MS);
+        assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(14.73333);
+
+        UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uid2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(90 * MINUTE_IN_MS);
+        assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(73.66666);
+    }
+
+    private void setFgState(int uid, boolean fgOn, long realtimeMs, long uptimeMs) {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+        if (fgOn) {
+            batteryStats.noteActivityResumedLocked(uid, realtimeMs, uptimeMs);
+            batteryStats.noteUidProcessStateLocked(uid, ActivityManager.PROCESS_STATE_TOP,
+                    realtimeMs, uptimeMs);
+        } else {
+            batteryStats.noteActivityPausedLocked(uid, realtimeMs, uptimeMs);
+            batteryStats.noteUidProcessStateLocked(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+                    realtimeMs, uptimeMs);
+        }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
new file mode 100644
index 0000000..6fa1d3b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.UserBatteryConsumer;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserPowerCalculatorTest {
+    public static final int USER1 = 0;
+    public static final int USER2 = 1625;
+
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;
+    private static final int APP_UID3 = Process.FIRST_APPLICATION_UID + 314;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+
+    @Test
+    public void testAllUsers() {
+        prepareUidBatteryConsumers();
+
+        UserPowerCalculator calculator = new UserPowerCalculator();
+
+        mStatsRule.apply(BatteryUsageStatsQuery.DEFAULT, calculator, new FakeAudioPowerCalculator(),
+                new FakeVideoPowerCalculator());
+
+        assertThat(mStatsRule.getUserBatteryConsumer(USER1)).isNull();
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(3000);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(7000);
+
+        assertThat(mStatsRule.getUserBatteryConsumer(USER2)).isNull();
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(5555);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(9999);
+    }
+
+    @Test
+    public void testSpecificUser() {
+        prepareUidBatteryConsumers();
+
+        UserPowerCalculator calculator = new UserPowerCalculator();
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder().addUser(UserHandle.of(USER1)).build(),
+                calculator, new FakeAudioPowerCalculator(), new FakeVideoPowerCalculator());
+
+        assertThat(mStatsRule.getUserBatteryConsumer(USER1)).isNull();
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(3000);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(7000);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID2))).isNull();
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID3))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(7070);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID3))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(11110);
+
+        UserBatteryConsumer user2 = mStatsRule.getUserBatteryConsumer(USER2);
+        assertThat(user2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO))
+                .isEqualTo(15308);
+        assertThat(user2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO))
+                .isEqualTo(24196);
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID1))).isNull();
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))).isNull();
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID3))).isNull();
+    }
+
+    private void prepareUidBatteryConsumers() {
+        prepareUidBatteryConsumer(USER1, APP_UID1, 1000, 2000, 3000, 4000);
+        prepareUidBatteryConsumer(USER2, APP_UID2, 2222, 3333, 4444, 5555);
+        prepareUidBatteryConsumer(USER1, APP_UID3, 3030, 4040, 5050, 6060);
+        prepareUidBatteryConsumer(USER2, APP_UID3, 4321, 5432, 6543, 7654);
+    }
+
+    private void prepareUidBatteryConsumer(int userId, int uid, long audioDuration1Ms,
+            long audioDuration2Ms, long videoDuration1Ms, long videoDuration2Ms) {
+        BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(UserHandle.getUid(userId, uid));
+
+        // Use "audio" and "video" to fake some power consumption. Could be any other type of usage.
+        uidStats.noteAudioTurnedOnLocked(0);
+        uidStats.noteAudioTurnedOffLocked(audioDuration1Ms);
+        uidStats.noteAudioTurnedOnLocked(1000000);
+        uidStats.noteAudioTurnedOffLocked(1000000 + audioDuration2Ms);
+
+        uidStats.noteVideoTurnedOnLocked(0);
+        uidStats.noteVideoTurnedOffLocked(videoDuration1Ms);
+        uidStats.noteVideoTurnedOnLocked(2000000);
+        uidStats.noteVideoTurnedOffLocked(2000000 + videoDuration2Ms);
+    }
+
+    private static class FakeAudioPowerCalculator extends PowerCalculator {
+        @Override
+        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+            long durationMs = u.getAudioTurnedOnTimer().getTotalTimeLocked(rawRealtimeUs, 0);
+            app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO, durationMs / 1000);
+        }
+    }
+
+    private static class FakeVideoPowerCalculator extends PowerCalculator {
+        @Override
+        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+            long durationMs = u.getVideoTurnedOnTimer().getTotalTimeLocked(rawRealtimeUs, 0);
+            app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO, durationMs / 1000);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index b9908f4..1679774 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -387,6 +387,24 @@
     }
 
     @Test
+    public void testIsValidCustomBucket() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+        assertFalse(stats.isValidCustomBucket(-1));
+        assertTrue(stats.isValidCustomBucket(0));
+        assertTrue(stats.isValidCustomBucket(1));
+        assertTrue(stats.isValidCustomBucket(2));
+        assertFalse(stats.isValidCustomBucket(3));
+        assertFalse(stats.isValidCustomBucket(4));
+
+        final MeasuredEnergyStats boringStats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+        assertFalse(boringStats.isValidCustomBucket(-1));
+        assertFalse(boringStats.isValidCustomBucket(0));
+        assertFalse(boringStats.isValidCustomBucket(1));
+    }
+
+    @Test
     public void testReset() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
         final int numCustomBuckets = 2;
diff --git a/core/tests/overlaytests/device/res/values/config.xml b/core/tests/overlaytests/device/res/values/config.xml
index e918268..a30d66f 100644
--- a/core/tests/overlaytests/device/res/values/config.xml
+++ b/core/tests/overlaytests/device/res/values/config.xml
@@ -2,6 +2,7 @@
 <resources>
     <string name="str">none</string>
     <string name="str2">none</string>
+    <integer name="overlaid">0</integer>
     <integer name="matrix_100000">100</integer>
     <integer name="matrix_100001">100</integer>
     <integer name="matrix_100010">100</integer>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
new file mode 100644
index 0000000..3465989
--- /dev/null
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2021 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.overlaytest;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+@MediumTest
+public class FabricatedOverlaysTest {
+    private static final String TAG = "FabricatedOverlaysTest";
+    private final String TEST_RESOURCE = "integer/overlaid";
+    private final String TEST_OVERLAY_NAME = "Test";
+
+    private Context mContext;
+    private Resources mResources;
+    private OverlayManager mOverlayManager;
+    private int mUserId;
+    private UserHandle mUserHandle;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResources = mContext.getResources();
+        mOverlayManager = mContext.getSystemService(OverlayManager.class);
+        mUserId = UserHandle.myUserId();
+        mUserHandle = UserHandle.of(mUserId);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
+        mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
+                info -> {
+                    if (info.isFabricated()) {
+                        cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
+                    }
+                });
+        mOverlayManager.commit(cleanUp.build());
+    }
+
+    @Test
+    public void testFabricatedOverlay() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+
+        OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertFalse(info.isEnabled());
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertTrue(info.isEnabled());
+
+        waitForResourceValue(1);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .unregisterFabricatedOverlay(overlay.getIdentifier())
+                .build());
+
+        waitForResourceValue(0);
+    }
+
+    @Test
+    public void testRegisterEnableAtomic() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        waitForResourceValue(1);
+    }
+
+    @Test
+    public void testRegisterTwice() throws Exception {
+        FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        waitForResourceValue(1);
+        overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 2)
+                .build();
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+        waitForResourceValue(2);
+    }
+
+    @Test
+    public void testInvalidOwningPackageName() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+            mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                    .registerFabricatedOverlay(overlay)
+                    .setEnabled(overlay.getIdentifier(), true, mUserId)
+                    .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testInvalidOverlayName() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), "invalid@name", mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testOverlayIdentifierLongest() throws Exception {
+        final int maxLength = 255 - 11; // 11 reserved characters
+        final String longestName = String.join("",
+                Collections.nCopies(maxLength - mContext.getPackageName().length(), "a"));
+        {
+            FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+                    longestName, mContext.getPackageName())
+                    .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                    .build();
+
+            mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                    .registerFabricatedOverlay(overlay)
+                    .build());
+            assertNotNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+        }
+        {
+            FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+                    longestName + "a", mContext.getPackageName())
+                    .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                    .build();
+
+            assertThrows(SecurityException.class, () ->
+                    mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                            .registerFabricatedOverlay(overlay)
+                            .build()));
+
+            assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+        }
+    }
+
+    @Test
+    public void testInvalidResourceValues() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testTransactionFailRollback() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .setEnabled(new OverlayIdentifier("not-valid"), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    void waitForResourceValue(final int expectedValue) throws TimeoutException {
+        final long timeOutDuration = 10000;
+        final long endTime = System.currentTimeMillis() + timeOutDuration;
+        final String resourceName = TEST_RESOURCE;
+        final int resourceId = mResources.getIdentifier(resourceName, "",
+                mContext.getPackageName());
+        int resourceValue = 0;
+        while (System.currentTimeMillis() < endTime) {
+            resourceValue = mResources.getInteger(resourceId);
+            if (resourceValue == expectedValue) {
+                return;
+            }
+        }
+        final String paths = TextUtils.join(",", mResources.getAssets().getApkPaths());
+        Log.w(TAG, "current paths: [" + paths + "]", new Throwable());
+        throw new TimeoutException("Timed out waiting for '" + resourceName + "' value to equal '"
+                + expectedValue + "': current value is '" + resourceValue + "'");
+    }
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
index 76c01a7..3c0c131 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayManager;
 import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
@@ -32,14 +33,14 @@
 class LocalOverlayManager {
     private static final long TIMEOUT = 30;
 
-    public static void toggleOverlaysAndWait(@NonNull final String[] overlaysToEnable,
-            @NonNull final String[] overlaysToDisable) throws Exception {
+    public static void toggleOverlaysAndWait(@NonNull final OverlayIdentifier[] overlaysToEnable,
+            @NonNull final OverlayIdentifier[] overlaysToDisable) throws Exception {
         final int userId = UserHandle.myUserId();
         OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
-        for (String pkg : overlaysToEnable) {
+        for (OverlayIdentifier pkg : overlaysToEnable) {
             builder.setEnabled(pkg, true, userId);
         }
-        for (String pkg : overlaysToDisable) {
+        for (OverlayIdentifier pkg : overlaysToDisable) {
             builder.setEnabled(pkg, false, userId);
         }
         OverlayManagerTransaction transaction = builder.build();
@@ -48,7 +49,7 @@
         FutureTask<Boolean> task = new FutureTask<>(() -> {
             while (true) {
                 final String[] paths = ctx.getResources().getAssets().getApkPaths();
-                if (arrayTailContains(paths, overlaysToEnable)
+                if (arrayTailContainsOverlays(paths, overlaysToEnable)
                         && arrayDoesNotContain(paths, overlaysToDisable)) {
                     return true;
                 }
@@ -64,15 +65,15 @@
         task.get(TIMEOUT, SECONDS);
     }
 
-    private static boolean arrayTailContains(@NonNull final String[] array,
-            @NonNull final String[] substrings) {
-        if (array.length < substrings.length) {
+    private static boolean arrayTailContainsOverlays(@NonNull final String[] array,
+            @NonNull final OverlayIdentifier[] overlays) {
+        if (array.length < overlays.length) {
             return false;
         }
-        for (int i = 0; i < substrings.length; i++) {
-            String a = array[array.length - substrings.length + i];
-            String s = substrings[i];
-            if (!a.contains(s)) {
+        for (int i = 0; i < overlays.length; i++) {
+            String a = array[array.length - overlays.length + i];
+            OverlayIdentifier s = overlays[i];
+            if (!a.contains(s.getPackageName())) {
                 return false;
             }
         }
@@ -80,10 +81,10 @@
     }
 
     private static boolean arrayDoesNotContain(@NonNull final String[] array,
-            @NonNull final String[] substrings) {
-        for (String s : substrings) {
+            @NonNull final OverlayIdentifier[] overlays) {
+        for (OverlayIdentifier s : overlays) {
             for (String a : array) {
-                if (a.contains(s)) {
+                if (a.contains(s.getPackageName())) {
                     return false;
                 }
             }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
index 636f4c8..8e4b9ef 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -58,9 +59,12 @@
     static final int MODE_SINGLE_OVERLAY = 1;
     static final int MODE_MULTIPLE_OVERLAYS = 2;
 
-    static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
-    static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
-    static final String FRAMEWORK_OVERLAY_PKG = "com.android.overlaytest.framework";
+    static final OverlayIdentifier APP_OVERLAY_ONE_PKG =
+            new OverlayIdentifier("com.android.overlaytest.app_overlay_one");
+    static final OverlayIdentifier APP_OVERLAY_TWO_PKG =
+            new OverlayIdentifier("com.android.overlaytest.app_overlay_two");
+    static final OverlayIdentifier FRAMEWORK_OVERLAY_PKG =
+            new OverlayIdentifier("com.android.overlaytest.framework");
 
     protected OverlayBaseTest(int mode) {
         mMode = mode;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
index 0b4f5e2..27d7342 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
@@ -16,14 +16,19 @@
 
 package com.android.overlaytest;
 
+import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_ONE_PKG;
+import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_TWO_PKG;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.testng.Assert.assertThrows;
 
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
 import android.content.om.OverlayManagerTransaction;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.UserHandle;
 
@@ -40,8 +45,6 @@
 @RunWith(JUnit4.class)
 @MediumTest
 public class TransactionTest {
-    static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
-    static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
 
     private Context mContext;
     private Resources mResources;
@@ -58,8 +61,8 @@
         mUserHandle = UserHandle.of(mUserId);
 
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{},
-                new String[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+                new OverlayIdentifier[]{},
+                new OverlayIdentifier[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
     }
 
     @Test
@@ -78,8 +81,8 @@
         List<OverlayInfo> ois =
                 mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
         assertEquals(ois.size(), 2);
-        assertEquals(ois.get(0).packageName, APP_OVERLAY_ONE_PKG);
-        assertEquals(ois.get(1).packageName, APP_OVERLAY_TWO_PKG);
+        assertEquals(ois.get(0).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
+        assertEquals(ois.get(1).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
 
         OverlayManagerTransaction t2 = new OverlayManagerTransaction.Builder()
                 .setEnabled(APP_OVERLAY_TWO_PKG, true)
@@ -92,8 +95,8 @@
         List<OverlayInfo> ois2 =
                 mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
         assertEquals(ois2.size(), 2);
-        assertEquals(ois2.get(0).packageName, APP_OVERLAY_TWO_PKG);
-        assertEquals(ois2.get(1).packageName, APP_OVERLAY_ONE_PKG);
+        assertEquals(ois2.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
+        assertEquals(ois2.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
 
         OverlayManagerTransaction t3 = new OverlayManagerTransaction.Builder()
                 .setEnabled(APP_OVERLAY_TWO_PKG, false)
@@ -105,8 +108,8 @@
         List<OverlayInfo> ois3 =
                 mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
         assertEquals(ois3.size(), 2);
-        assertEquals(ois3.get(0).packageName, APP_OVERLAY_TWO_PKG);
-        assertEquals(ois3.get(1).packageName, APP_OVERLAY_ONE_PKG);
+        assertEquals(ois3.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
+        assertEquals(ois3.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
     }
 
     @Test
@@ -116,7 +119,7 @@
 
         OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
                 .setEnabled(APP_OVERLAY_ONE_PKG, true)
-                .setEnabled("does-not-exist", true)
+                .setEnabled(new OverlayIdentifier("does-not-exist"), true)
                 .setEnabled(APP_OVERLAY_TWO_PKG, true)
                 .build();
         assertThrows(SecurityException.class, () -> mOverlayManager.commit(t));
@@ -125,9 +128,10 @@
         assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
     }
 
-    private void assertOverlayIsEnabled(final String packageName, boolean enabled, int userId) {
-        final OverlayInfo oi = mOverlayManager.getOverlayInfo(packageName, UserHandle.of(userId));
+    private void assertOverlayIsEnabled(final OverlayIdentifier overlay, boolean enabled,
+            int userId) {
+        final OverlayInfo oi = mOverlayManager.getOverlayInfo(overlay, UserHandle.of(userId));
         assertNotNull(oi);
-        assertEquals(oi.isEnabled(), enabled);
+        assertEquals(enabled, oi.isEnabled());
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
index 420f755..5587203 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
@@ -16,6 +16,8 @@
 
 package com.android.overlaytest;
 
+import android.content.om.OverlayIdentifier;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
@@ -32,7 +34,9 @@
     @BeforeClass
     public static void enableOverlay() throws Exception {
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG},
-                new String[]{});
+                new OverlayIdentifier[]{
+                        FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
+                },
+                new OverlayIdentifier[]{});
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
index a86255e..d275433 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
@@ -16,6 +16,8 @@
 
 package com.android.overlaytest;
 
+import android.content.om.OverlayIdentifier;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
@@ -32,7 +34,7 @@
     @BeforeClass
     public static void enableOverlays() throws Exception {
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
-                new String[]{APP_OVERLAY_TWO_PKG});
+                new OverlayIdentifier[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
+                new OverlayIdentifier[]{APP_OVERLAY_TWO_PKG});
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
index 51c4118..72cba8b 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -16,6 +16,8 @@
 
 package com.android.overlaytest;
 
+import android.content.om.OverlayIdentifier;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
@@ -32,7 +34,9 @@
     @BeforeClass
     public static void disableOverlays() throws Exception {
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{},
-                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+                new OverlayIdentifier[]{},
+                new OverlayIdentifier[]{
+                        FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
+                });
     }
 }
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
index 38e5fa1..926b186 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
@@ -25,7 +25,6 @@
     <item target="integer/matrix_100100" value="@integer/matrix_100100"/>
     <item target="integer/matrix_100101" value="@integer/matrix_100101"/>
     <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
-    <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
     <item target="integer/matrix_100111" value="@integer/matrix_100111"/>
     <item target="integer/matrix_101000" value="@integer/matrix_101000"/>
     <item target="integer/matrix_101001" value="@integer/matrix_101001"/>
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index cf0199b..c940574 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -26,6 +26,7 @@
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+        <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_DEBUGGING"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
@@ -40,10 +41,12 @@
         <permission name="android.permission.MOVE_PACKAGE"/>
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.REQUEST_NETWORK_SCORES"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
         <permission name="android.permission.SET_TIME"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index 19548bc..48f6ab3 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -25,6 +25,7 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.READ_LOGS"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 3a20a9c..bd30d7a 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -27,8 +27,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
         <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
         <permission name="android.permission.LOCATION_HARDWARE"/>
+        <permission name="android.permission.LOCK_DEVICE"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MASTER_CLEAR"/>
         <!-- use for CarServiceTest -->
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
@@ -40,9 +42,11 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.RESET_PASSWORD"/>
         <permission name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/>
         <!-- use for CarServiceTest -->
         <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
 
         <!-- use for rotary fragment to enable/disable packages related to rotary -->
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e473c55..3fdb0da 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -58,5 +58,6 @@
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index cb4dd9e..b70fa0e 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -46,7 +46,6 @@
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.Optional;
 import java.util.concurrent.Executor;
 import java.util.stream.Stream;
@@ -1148,24 +1147,14 @@
                             // Default to SRGB if the display doesn't support wide color
                             .orElse(Dataspace.SRGB);
 
-            float maxRefreshRate =
-                    (float) Arrays.stream(display.getSupportedModes())
-                            .mapToDouble(Mode::getRefreshRate)
-                            .max()
-                            .orElseGet(() -> {
-                                Log.i(LOG_TAG, "Failed to find the maximum display refresh rate");
-                                // Assume that the max refresh rate is 60hz if we can't find one.
-                                return 60.0;
-                            });
             // Grab the physical screen dimensions from the active display mode
             // Strictly speaking the screen resolution may not always be constant - it is for
             // sizing the font cache for the underlying rendering thread. Since it's a
             // heuristic we don't need to be always 100% correct.
             Mode activeMode = display.getMode();
             nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
-                    display.getRefreshRate(), maxRefreshRate,
-                    wideColorDataspace.mNativeDataspace, display.getAppVsyncOffsetNanos(),
-                    display.getPresentationDeadlineNanos());
+                    display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
+                    display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
 
             // Defensively clear out the context
             mContext = null;
@@ -1324,6 +1313,5 @@
     private static native void nSetDisplayDensityDpi(int densityDpi);
 
     private static native void nInitDisplayInfo(int width, int height, float refreshRate,
-            float maxRefreshRate, int wideColorDataspace, long appVsyncOffsetNanos,
-            long presentationDeadlineNanos);
+            int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos);
 }
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index cf2f970..25f76f6 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -96,6 +97,27 @@
     }
 
     /**
+     * @return Returns a {@link String} that represents this point which can be parsed with
+     * {@link #unflattenFromString(String)}.
+     * @hide
+     */
+    @NonNull
+    public String flattenToString() {
+        return x + "x" + y;
+    }
+
+    /**
+     * @return Returns a {@link Point} from a short string created from {@link #flattenToString()}.
+     * @hide
+     */
+    @Nullable
+    public static Point unflattenFromString(String s) throws NumberFormatException {
+        final int sep_ix = s.indexOf("x");
+        return new Point(Integer.parseInt(s.substring(0, sep_ix)),
+                Integer.parseInt(s.substring(sep_ix + 1)));
+    }
+
+    /**
      * Parcelable interface methods
      */
     @Override
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 35e6b859..40c75a4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1414,6 +1414,16 @@
         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
     }
 
+    /** @hide */
+    public List<FontFamily> getFallback() {
+        ArrayList<FontFamily> families = new ArrayList<>();
+        int familySize = nativeGetFamilySize(native_instance);
+        for (int i = 0; i < familySize; ++i) {
+            families.add(new FontFamily(nativeGetFamily(native_instance, i)));
+        }
+        return families;
+    }
+
     private static native long nativeCreateFromTypeface(long native_instance, int style);
     private static native long nativeCreateFromTypefaceWithExactStyle(
             long native_instance, int weight, boolean italic);
@@ -1439,6 +1449,13 @@
     @CriticalNative
     private static native long nativeGetReleaseFunc();
 
+    @CriticalNative
+    private static native int nativeGetFamilySize(long naitvePtr);
+
+    @CriticalNative
+    private static native long nativeGetFamily(long nativePtr, int index);
+
+
     private static native void nativeRegisterGenericFamily(String str, long nativePtr);
 
     private static native int nativeWriteTypefaces(
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 8c13d3e..a771a6e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -125,7 +125,7 @@
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
-            final FontFamily family = new FontFamily(mFonts, ptr);
+            final FontFamily family = new FontFamily(ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
         }
@@ -146,7 +146,8 @@
     private final long mNativePtr;
 
     // Use Builder instead.
-    private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+    /** @hide */
+    public FontFamily(long ptr) {
         mNativePtr = ptr;
     }
 
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 904085f..255f9e6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -68,44 +68,23 @@
      */
     public static @NonNull Set<Font> getAvailableFonts() {
         synchronized (LOCK) {
-            if (sAvailableFonts != null) {
-                return sAvailableFonts;
-            }
-
-            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
-                sAvailableFonts = collectAllFonts();
-            } else {
+            if (sAvailableFonts == null) {
                 Set<Font> set = new ArraySet<>();
-                for (FontFamily[] items : sFamilyMap.values()) {
-                    for (FontFamily family : items) {
-                        for (int i = 0; i < family.getSize(); ++i) {
-                            set.add(family.getFont(i));
+                for (Typeface tf : Typeface.getSystemFontMap().values()) {
+                    List<FontFamily> families = tf.getFallback();
+                    for (int i = 0; i < families.size(); ++i) {
+                        FontFamily family = families.get(i);
+                        for (int j = 0; j < family.getSize(); ++j) {
+                            set.add(family.getFont(j));
                         }
                     }
                 }
-
                 sAvailableFonts = Collections.unmodifiableSet(set);
             }
             return sAvailableFonts;
         }
     }
 
-    private static @NonNull Set<Font> collectAllFonts() {
-        // TODO: use updated fonts
-        FontConfig fontConfig = getSystemPreinstalledFontConfig();
-        Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
-
-        Set<Font> res = new ArraySet<>();
-        for (FontFamily[] families : map.values()) {
-            for (FontFamily family : families) {
-                for (int i = 0; i < family.getSize(); ++i) {
-                    res.add(family.getFont(i));
-                }
-            }
-        }
-        return res;
-    }
-
     private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
         try (FileInputStream file = new FileInputStream(fullPath)) {
             final FileChannel fileChannel = file.getChannel();
@@ -329,13 +308,4 @@
         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
         return result;
     }
-
-    /**
-     * @hide
-     */
-    public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
-        synchronized (LOCK) {
-            sFamilyMap = fallbackMap;
-        }
-    }
 }
diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java
index 372add9..d188b65 100644
--- a/keystore/java/android/security/KeyStoreSecurityLevel.java
+++ b/keystore/java/android/security/KeyStoreSecurityLevel.java
@@ -190,7 +190,7 @@
         keyDescriptor.blob = wrappedKey;
         keyDescriptor.domain = wrappedKeyDescriptor.domain;
 
-        return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor,
+        return handleExceptions(() -> mSecurityLevel.importWrappedKey(keyDescriptor,
                 wrappingKeyDescriptor, maskingKey,
                 args.toArray(new KeyParameter[args.size()]), authenticatorSpecs));
     }
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 16bf546..0871517 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -434,14 +434,16 @@
     @NonNull
     public static java.security.KeyStore getKeyStoreForUid(int uid)
             throws KeyStoreException, NoSuchProviderException {
-        String providerName = PROVIDER_NAME;
+        final java.security.KeyStore.LoadStoreParameter loadParameter;
         if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
-            providerName = "AndroidKeyStoreLegacy";
+            loadParameter = new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
+                    KeyProperties.legacyUidToNamespace(uid));
+        } else {
+            loadParameter = new AndroidKeyStoreLoadStoreParameter(uid);
         }
-        java.security.KeyStore result =
-                java.security.KeyStore.getInstance(providerName);
+        java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME);
         try {
-            result.load(new AndroidKeyStoreLoadStoreParameter(uid));
+            result.load(loadParameter);
         } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
             throw new KeyStoreException(
                     "Failed to load AndroidKeyStore KeyStore for UID " + uid, e);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
index 8475ad9..0f77749 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
@@ -164,6 +164,9 @@
 
         List<KeyParameter> parameters = new ArrayList<>();
         parameters.add(KeyStore2ParameterUtils.makeEnum(
+                KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_SIGN
+        ));
+        parameters.add(KeyStore2ParameterUtils.makeEnum(
                 KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC
         ));
         parameters.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
index 32650ae..5619585 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
@@ -21,7 +21,6 @@
 import android.system.keystore2.Authorization;
 import android.system.keystore2.Domain;
 import android.system.keystore2.KeyDescriptor;
-import android.util.Log;
 
 import java.security.Key;
 
@@ -127,15 +126,6 @@
             return false;
         }
 
-        // If the key ids are equal and the class matches all the other fields cannot differ
-        // unless we have a bug.
-        if (!mAlgorithm.equals(other.mAlgorithm)
-                || !mAuthorizations.equals(other.mAuthorizations)
-                || !mDescriptor.equals(other.mDescriptor)) {
-            Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata"
-                    + "differs.");
-            return false;
-        }
         return true;
     }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 8c8acc4..39607ae 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -866,7 +866,8 @@
         try {
             response = mKeyStore.getKeyEntry(wrappingkey);
         } catch (android.security.KeyStoreException e) {
-            throw new KeyStoreException("Failed to load wrapping key.", e);
+            throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
+                    + e.getErrorCode(), e);
         }
 
         KeyDescriptor wrappedKey = makeKeyDescriptor(alias);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index aa82339..73fd693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,24 +16,16 @@
 
 package com.android.wm.shell;
 
-import android.util.Slog;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 
 /**
  * An entry point into the shell for dumping shell internal state and running adb commands.
  *
  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
  */
+@ExternalThread
 public interface ShellCommandHandler {
     /**
      * Dumps the shell state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 982cc00..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.io.PrintWriter;
@@ -38,24 +37,24 @@
 public final class ShellCommandHandlerImpl {
     private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
 
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
-    private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutout;
+    private final Optional<OneHandedController> mOneHandedOptional;
+    private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<AppPairs> mAppPairsOptional;
     private final ShellExecutor mMainExecutor;
     private final HandlerImpl mImpl = new HandlerImpl();
 
     public static ShellCommandHandler create(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
                 splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
@@ -64,12 +63,12 @@
 
     private ShellCommandHandlerImpl(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mLegacySplitScreenOptional = legacySplitScreenOptional;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 925bf4b..7376d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -39,9 +38,9 @@
     private final DisplayImeController mDisplayImeController;
     private final DragAndDropController mDragAndDropController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private final Optional<AppPairs> mAppPairsOptional;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
@@ -51,9 +50,9 @@
     public static ShellInit create(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -71,9 +70,9 @@
     private ShellInitImpl(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -97,7 +96,7 @@
         // Register the shell organizer
         mShellTaskOrganizer.registerOrganizer();
 
-        mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+        mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
         mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
 
         // Bind the splitscreen impl to the drag drop controller
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index a570c0a..b22f358 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,7 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import java.io.PrintWriter;
@@ -108,20 +108,20 @@
      * compat.
      */
     @Nullable
-    private final SizeCompatUI mSizeCompatUI;
+    private final SizeCompatUIController mSizeCompatUI;
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
         this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            SizeCompatUI sizeCompatUI) {
+            SizeCompatUIController sizeCompatUI) {
         this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUI sizeCompatUI) {
+            Context context, @Nullable SizeCompatUIController sizeCompatUI) {
         super(taskOrganizerController, mainExecutor);
         // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
         //  by a controller, that class should be create while porting
@@ -342,8 +342,8 @@
     }
 
     /**
-     * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
-     * the UI accordingly.
+     * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+     * to update the UI accordingly.
      *
      * @param taskInfo the new Task info
      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index a5dd79b..58ca1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -30,6 +30,7 @@
 public class TaskViewFactoryController {
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
+    private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
             ShellExecutor shellExecutor) {
@@ -37,8 +38,11 @@
         mShellExecutor = shellExecutor;
     }
 
+    public TaskViewFactory asTaskViewFactory() {
+        return mImpl;
+    }
+
     /** Creates an {@link TaskView} */
-    @ShellMainThread
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
         TaskView taskView = new TaskView(context, mTaskOrganizer);
         executor.execute(() -> {
@@ -46,10 +50,6 @@
         });
     }
 
-    public TaskViewFactory getTaskViewFactory() {
-        return new TaskViewFactoryImpl();
-    }
-
     private class TaskViewFactoryImpl implements TaskViewFactory {
         @ExternalThread
         public void create(@UiContext Context context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index abd9257..59271e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -27,6 +27,7 @@
 /**
  * The singleton wrapper to communicate between WindowManagerService and WMShell features
  * (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ * TODO: Remove once PinnedStackListenerForwarder can be removed
  */
 public class WindowManagerShellWrapper {
     private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index f5aa852..a9b1dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -35,8 +35,4 @@
     boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
     /** Unpairs any app-pair containing this task id. */
     void unpair(int taskId);
-    /** Dumps current status of app pairs. */
-    void dump(@NonNull PrintWriter pw, String prefix);
-    /** Called when the shell organizer has been registered. */
-    void onOrganizerRegistered();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index e380426..0415f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -51,18 +51,7 @@
     private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
     private final DisplayController mDisplayController;
 
-    /**
-     * Creates {@link AppPairs}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static AppPairs create(ShellTaskOrganizer organizer,
-            SyncTransactionQueue syncQueue, DisplayController displayController,
-            ShellExecutor mainExecutor) {
-        return new AppPairsController(organizer, syncQueue, displayController,
-                mainExecutor).mImpl;
-    }
-
-    AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
+    public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
                 DisplayController displayController, ShellExecutor mainExecutor) {
         mTaskOrganizer = organizer;
         mSyncQueue = syncQueue;
@@ -70,18 +59,22 @@
         mMainExecutor = mainExecutor;
     }
 
-    void onOrganizerRegistered() {
+    public AppPairs asAppPairs() {
+        return mImpl;
+    }
+
+    public void onOrganizerRegistered() {
         if (mPairsPool == null) {
             setPairsPool(new AppPairsPool(this));
         }
     }
 
     @VisibleForTesting
-    void setPairsPool(AppPairsPool pool) {
+    public void setPairsPool(AppPairsPool pool) {
         mPairsPool = pool;
     }
 
-    boolean pair(int taskId1, int taskId2) {
+    public boolean pair(int taskId1, int taskId2) {
         final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
         final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
         if (task1 == null || task2 == null) {
@@ -90,13 +83,13 @@
         return pair(task1, task2);
     }
 
-    boolean pair(ActivityManager.RunningTaskInfo task1,
+    public boolean pair(ActivityManager.RunningTaskInfo task1,
             ActivityManager.RunningTaskInfo task2) {
         return pairInner(task1, task2) != null;
     }
 
     @VisibleForTesting
-    AppPair pairInner(
+    public AppPair pairInner(
             @NonNull ActivityManager.RunningTaskInfo task1,
             @NonNull ActivityManager.RunningTaskInfo task2) {
         final AppPair pair = mPairsPool.acquire();
@@ -109,11 +102,11 @@
         return pair;
     }
 
-    void unpair(int taskId) {
+    public void unpair(int taskId) {
         unpair(taskId, true /* releaseToPool */);
     }
 
-    void unpair(int taskId, boolean releaseToPool) {
+    public void unpair(int taskId, boolean releaseToPool) {
         AppPair pair = mActiveAppPairs.get(taskId);
         if (pair == null) {
             for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
@@ -137,19 +130,19 @@
         }
     }
 
-    ShellTaskOrganizer getTaskOrganizer() {
+    public ShellTaskOrganizer getTaskOrganizer() {
         return mTaskOrganizer;
     }
 
-    SyncTransactionQueue getSyncTransactionQueue() {
+    public SyncTransactionQueue getSyncTransactionQueue() {
         return mSyncQueue;
     }
 
-    DisplayController getDisplayController() {
+    public DisplayController getDisplayController() {
         return mDisplayController;
     }
 
-    private void dump(@NonNull PrintWriter pw, String prefix) {
+    public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
@@ -202,21 +195,5 @@
                 AppPairsController.this.unpair(taskId);
             });
         }
-
-        @Override
-        public void onOrganizerRegistered() {
-            mMainExecutor.execute(() -> {
-                AppPairsController.this.onOrganizerRegistered();
-            });
-        }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw, String prefix) {
-            try {
-                mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump AppPairsController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 40fdb97..0ee1f06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -398,6 +398,14 @@
         }
     }
 
+    @Override
+    public void setExpandedContentAlpha(float alpha) {
+        if (mExpandedView != null) {
+            mExpandedView.setAlpha(alpha);
+            mExpandedView.setTaskViewAlpha(alpha);
+        }
+    }
+
     /**
      * Set visibility of bubble in the expanded state.
      *
@@ -407,7 +415,7 @@
      * and setting {@code false} actually means rendering the expanded view in transparent.
      */
     @Override
-    public void setContentVisibility(boolean visibility) {
+    public void setTaskViewVisibility(boolean visibility) {
         if (mExpandedView != null) {
             mExpandedView.setContentVisibility(visibility);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d73fc6d..2391a08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -192,9 +192,9 @@
     private boolean mIsStatusBarShade = true;
 
     /**
-     * Injected constructor.
+     * Creates an instance of the BubbleController.
      */
-    public static Bubbles create(Context context,
+    public static BubbleController create(Context context,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             @Nullable IStatusBarService statusBarService,
@@ -211,14 +211,14 @@
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
+                logger, organizer, positioner, mainExecutor, mainHandler);
     }
 
     /**
      * Testing constructor.
      */
     @VisibleForTesting
-    public BubbleController(Context context,
+    protected BubbleController(Context context,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -322,7 +322,7 @@
     }
 
     @VisibleForTesting
-    public Bubbles getImpl() {
+    public Bubbles asBubbles() {
         return mImpl;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 29458ef..2f31acd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -85,6 +85,19 @@
     private boolean mImeVisible;
     private boolean mNeedsNewHeight;
 
+    /**
+     * Whether we want the TaskView's content to be visible (alpha = 1f). If
+     * {@link #mIsAlphaAnimating} is true, this may not reflect the TaskView's actual alpha value
+     * until the animation ends.
+     */
+    private boolean mIsContentVisible = false;
+
+    /**
+     * Whether we're animating the TaskView's alpha value. If so, we will hold off on applying alpha
+     * changes from {@link #setContentVisibility} until the animation ends.
+     */
+    private boolean mIsAlphaAnimating = false;
+
     private int mMinHeight;
     private int mOverflowHeight;
     private int mSettingsIconHeight;
@@ -150,6 +163,7 @@
                         // Apply flags to make behaviour match documentLaunchMode=always.
                         fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                         fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                        fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true);
                         if (mBubble != null) {
                             mBubble.setIntentActive();
                         }
@@ -465,6 +479,29 @@
     }
 
     /**
+     * Whether we are currently animating the TaskView's alpha value. If this is set to true, calls
+     * to {@link #setContentVisibility} will not be applied until this is set to false again.
+     */
+    void setAlphaAnimating(boolean animating) {
+        mIsAlphaAnimating = animating;
+
+        // If we're done animating, apply the correct
+        if (!animating) {
+            setContentVisibility(mIsContentVisible);
+        }
+    }
+
+    /**
+     * Sets the alpha of the underlying TaskView, since changing the expanded view's alpha does not
+     * affect the TaskView since it uses a Surface.
+     */
+    void setTaskViewAlpha(float alpha) {
+        if (mTaskView != null) {
+            mTaskView.setAlpha(alpha);
+        }
+    }
+
+    /**
      * Set visibility of contents in the expanded state.
      *
      * @param visibility {@code true} if the contents should be visible on the screen.
@@ -477,16 +514,19 @@
             Log.d(TAG, "setContentVisibility: visibility=" + visibility
                     + " bubble=" + getBubbleKey());
         }
+        mIsContentVisible = visibility;
+
         final float alpha = visibility ? 1f : 0f;
 
         mPointerView.setAlpha(alpha);
-        if (mTaskView != null) {
+        if (mTaskView != null && !mIsAlphaAnimating) {
             mTaskView.setAlpha(alpha);
         }
     }
 
+
     @Nullable
-    View getTaskView() {
+    TaskView getTaskView() {
         return mTaskView;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 16cd3cf..51d63cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -167,8 +167,12 @@
         return dotPath
     }
 
-    override fun setContentVisibility(visible: Boolean) {
-        expandedView?.setContentVisibility(visible)
+    override fun setExpandedContentAlpha(alpha: Float) {
+        expandedView?.alpha = alpha
+    }
+
+    override fun setTaskViewVisibility(visible: Boolean) {
+        // Overflow does not have a TaskView.
     }
 
     override fun getIconView(): BadgedImageView? {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d54be0e..a3edc20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -43,7 +43,6 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Bundle;
-import android.os.Handler;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.Choreographer;
@@ -123,6 +122,10 @@
     @VisibleForTesting
     static final int FLYOUT_HIDE_AFTER = 5000;
 
+    private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
+
+    private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+
     /**
      * How long to wait to animate the stack temporarily invisible after a drag/flyout hide
      * animation ends, if we are in fact temporarily invisible.
@@ -142,7 +145,7 @@
 
     private final PhysicsAnimator.SpringConfig mTranslateSpringConfig =
             new PhysicsAnimator.SpringConfig(
-                    SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+                    SpringForce.STIFFNESS_VERY_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
 
     /**
      * Handler to use for all delayed animations - this way, we can easily cancel them before
@@ -211,6 +214,9 @@
     /** Container for the animating-out SurfaceView. */
     private FrameLayout mAnimatingOutSurfaceContainer;
 
+    /** Animator for animating the alpha value of the animating out SurfaceView. */
+    private final ValueAnimator mAnimatingOutSurfaceAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
     /**
      * Buffer containing a screenshot of the animating-out bubble. This is drawn into the
      * SurfaceView during animations.
@@ -261,6 +267,12 @@
     /** Whether we're in the middle of dragging the stack around by touch. */
     private boolean mIsDraggingStack = false;
 
+    /** Whether the expanded view has been hidden, because we are dragging out a bubble. */
+    private boolean mExpandedViewHidden = false;
+
+    /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
+    private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
     /**
      * The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
      * touches from other pointer indices.
@@ -614,6 +626,12 @@
             // Show the dismiss target, if we haven't already.
             mDismissView.show();
 
+            if (mIsExpanded && mExpandedBubble != null && v.equals(mExpandedBubble.getIconView())) {
+                // Hide the expanded view if we're dragging out the expanded bubble, and we haven't
+                // already hidden it.
+                hideExpandedViewIfNeeded();
+            }
+
             // First, see if the magnetized object consumes the event - if so, we shouldn't move the
             // bubble since it's stuck to the target.
             if (!passEventToMagnetizedObject(ev)) {
@@ -645,6 +663,9 @@
             if (!passEventToMagnetizedObject(ev)) {
                 if (mBubbleData.isExpanded()) {
                     mExpandedAnimationController.snapBubbleBack(v, velX, velY);
+
+                    // Re-show the expanded view if we hid it.
+                    showExpandedViewIfNeeded();
                 } else {
                     // Fling the stack to the edge, and save whether or not it's going to end up on
                     // the left side of the screen.
@@ -937,6 +958,46 @@
         animate()
                 .setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
                 .setDuration(FADE_IN_DURATION);
+
+        mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+        mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+        mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                    // We need to be Z ordered on top in order for alpha animations to work.
+                    mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
+                    mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                    mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+                    mExpandedBubble.getExpandedView().setAlphaAnimating(false);
+                }
+            }
+        });
+        mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
+            if (mExpandedBubble != null) {
+                mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue());
+            }
+        });
+
+        mAnimatingOutSurfaceAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+        mAnimatingOutSurfaceAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+        mAnimatingOutSurfaceAlphaAnimator.addUpdateListener(valueAnimator -> {
+            if (!mExpandedViewHidden) {
+                mAnimatingOutSurfaceView.setAlpha((float) valueAnimator.getAnimatedValue());
+            }
+        });
+        mAnimatingOutSurfaceAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                releaseAnimatingOutBubbleBuffer();
+            }
+        });
     }
 
     /**
@@ -1539,7 +1600,8 @@
 
         // If we're expanded, screenshot the currently expanded bubble (before expanding the newly
         // selected bubble) so we can animate it out.
-        if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+        if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null
+                && !mExpandedViewHidden) {
             if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
                 // Before screenshotting, have the real ActivityView show on top of other surfaces
                 // so that the screenshot doesn't flicker on top of it.
@@ -1575,7 +1637,7 @@
             mExpandedViewContainer.setAlpha(0.0f);
             mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
                 if (previouslySelected != null) {
-                    previouslySelected.setContentVisibility(false);
+                    previouslySelected.setTaskViewVisibility(false);
                 }
 
                 updateExpandedBubble();
@@ -1656,6 +1718,58 @@
         requestUpdate();
     }
 
+    /** Animate the expanded view hidden. This is done while we're dragging out a bubble. */
+    private void hideExpandedViewIfNeeded() {
+        if (mExpandedViewHidden
+                || mExpandedBubble == null
+                || mExpandedBubble.getExpandedView() == null) {
+            return;
+        }
+
+        mExpandedViewHidden = true;
+
+        // Scale down.
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
+                .addUpdateListener((target, values) ->
+                        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix))
+                .start();
+
+        // Animate alpha from 1f to 0f.
+        mExpandedViewAlphaAnimator.reverse();
+    }
+
+    /**
+     * Animate the expanded view visible again. This is done when we're done dragging out a bubble.
+     */
+    private void showExpandedViewIfNeeded() {
+        if (!mExpandedViewHidden) {
+            return;
+        }
+
+        mExpandedViewHidden = false;
+
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                        mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                        mScaleOutSpringConfig)
+                .addUpdateListener((target, values) ->
+                        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix))
+                .start();
+
+        mExpandedViewAlphaAnimator.start();
+    }
+
     private void animateExpansion() {
         cancelDelayedExpandCollapseSwitchAnimations();
         final boolean showVertically = mPositioner.showBubblesVertically();
@@ -1714,7 +1828,7 @@
         // Should not happen since we lay out before expanding, but just in case...
         if (getWidth() > 0) {
             startDelay = (long)
-                    (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION
+                    (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 1.2f
                             + (distanceAnimated / getWidth()) * 30);
         }
 
@@ -1728,20 +1842,29 @@
                 pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
             }
             mExpandedViewContainerMatrix.setScale(
-                    0f, 0f,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     pivotX, pivotY);
         } else {
             mExpandedViewContainerMatrix.setScale(
-                    0f, 0f,
-                    bubbleWillBeAt + mBubbleSize / 2f, getExpandedViewY());
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    bubbleWillBeAt + mBubbleSize / 2f,
+                    getExpandedViewY());
         }
         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
 
-        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+        if (mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.setExpandedContentAlpha(0f);
+
+            // We'll be starting the alpha animation after a slight delay, so set this flag early
+            // here.
+            mExpandedBubble.getExpandedView().setAlphaAnimating(true);
         }
 
         mDelayedAnimationExecutor.executeDelayed(() -> {
+            mExpandedViewAlphaAnimator.start();
+
             PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
             PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
                     .spring(AnimatableScaleMatrix.SCALE_X,
@@ -1796,22 +1919,15 @@
         // since we're about to animate collapsed.
         mExpandedAnimationController.notifyPreparingToCollapse();
 
-        final long startDelay =
-                (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
-        mDelayedAnimationExecutor.executeDelayed(() -> {
-            mExpandedAnimationController.collapseBackToStack(
-                    mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
-                    /* collapseTo */,
-                    () -> mBubbleContainer.setActiveController(mStackAnimationController));
-        }, startDelay);
+        mExpandedAnimationController.collapseBackToStack(
+                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                /* collapseTo */,
+                () -> mBubbleContainer.setActiveController(mStackAnimationController));
 
         if (mTaskbarScrim.getVisibility() == VISIBLE) {
             mTaskbarScrim.animate().alpha(0f).start();
         }
 
-        // We want to visually collapse into this bubble during the animation.
-        final View expandingFromBubble = mExpandedBubble.getIconView();
-
         int index;
         if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
             index = mBubbleData.getBubbles().size();
@@ -1840,31 +1956,25 @@
                     getExpandedViewY());
         }
 
+        mExpandedViewAlphaAnimator.reverse();
+
+        // When the animation completes, we should no longer be showing the content.
+        if (mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.getExpandedView().setContentVisibility(false);
+        }
+
         PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
         PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
-                .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig)
-                .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
                 .addUpdateListener((target, values) -> {
-                    if (expandingFromBubble != null) {
-                        // Follow the bubble as it translates!
-                        if (showVertically) {
-                            mExpandedViewContainerMatrix.postTranslate(
-                                    0f, expandingFromBubble.getTranslationY()
-                                            - expandingFromBubbleAt);
-                        } else {
-                            mExpandedViewContainerMatrix.postTranslate(
-                                    expandingFromBubble.getTranslationX()
-                                            - expandingFromBubbleAt, 0f);
-                        }
-                    }
-
                     mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-
-                    // Hide early so we don't have a tiny little expanded view still visible at the
-                    // end of the scale animation.
-                    if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) {
-                        mExpandedViewContainer.setVisibility(View.INVISIBLE);
-                    }
                 })
                 .withEndActions(() -> {
                     final BubbleViewProvider previouslySelected = mExpandedBubble;
@@ -1882,7 +1992,7 @@
                     updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */);
                     afterExpandedViewAnimation();
                     if (previouslySelected != null) {
-                        previouslySelected.setContentVisibility(false);
+                        previouslySelected.setTaskViewVisibility(false);
                     }
 
                     if (mPositioner.showingInTaskbar()) {
@@ -1903,22 +2013,21 @@
         // The surface contains a screenshot of the animating out bubble, so we just need to animate
         // it out (and then release the GraphicBuffer).
         PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
-        PhysicsAnimator animator = PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
-                .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig)
-                .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
-                .withEndActions(this::releaseAnimatingOutBubbleBuffer);
+
+        mAnimatingOutSurfaceAlphaAnimator.reverse();
+        mExpandedViewAlphaAnimator.start();
 
         if (mPositioner.showBubblesVertically()) {
             float translationX = mStackAnimationController.isStackOnLeftSide()
                     ? mAnimatingOutSurfaceContainer.getTranslationX() + mBubbleSize * 2
                     : mAnimatingOutSurfaceContainer.getTranslationX();
-            animator.spring(DynamicAnimation.TRANSLATION_X,
-                    translationX,
-                    mTranslateSpringConfig)
+            PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+                    .spring(DynamicAnimation.TRANSLATION_X, translationX, mTranslateSpringConfig)
                     .start();
         } else {
-            animator.spring(DynamicAnimation.TRANSLATION_Y,
-                    mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2,
+            PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+                    .spring(DynamicAnimation.TRANSLATION_Y,
+                            mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize,
                     mTranslateSpringConfig)
                     .start();
         }
@@ -1947,7 +2056,8 @@
                     pivotX, pivotY);
         } else {
             mExpandedViewContainerMatrix.setScale(
-                    0f, 0f,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     expandingFromBubbleDestination + mBubbleSize / 2f,
                     getExpandedViewY());
         }
@@ -1972,10 +2082,7 @@
                         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
                     })
                     .withEndActions(() -> {
-                        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
-                        }
-
+                        mExpandedViewHidden = false;
                         mIsBubbleSwitchAnimating = false;
                     })
                     .start();
@@ -2489,6 +2596,7 @@
                 && mExpandedBubble.getExpandedView() != null) {
             BubbleExpandedView bev = mExpandedBubble.getExpandedView();
             bev.setContentVisibility(false);
+            bev.setAlphaAnimating(!mIsExpansionAnimating);
             mExpandedViewContainerMatrix.setScaleX(0f);
             mExpandedViewContainerMatrix.setScaleY(0f);
             mExpandedViewContainerMatrix.setTranslate(0f, 0f);
@@ -2586,7 +2694,14 @@
                     mAnimatingOutBubbleBuffer.getHardwareBuffer(),
                     mAnimatingOutBubbleBuffer.getColorSpace());
 
-            mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true)));
+            mAnimatingOutSurfaceView.setAlpha(1f);
+            mExpandedViewContainer.setVisibility(View.GONE);
+
+            mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+                post(() -> {
+                    onComplete.accept(true);
+                });
+            });
         });
     }
 
@@ -2618,7 +2733,9 @@
             }
         }
         mExpandedViewContainer.setPadding(leftPadding, 0, rightPadding, 0);
-        mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+        if (mIsExpansionAnimating) {
+            mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+        }
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             mExpandedViewContainer.setTranslationY(getExpandedViewY());
             mExpandedViewContainer.setTranslationX(0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index ec900be..da4259c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -29,7 +29,16 @@
 public interface BubbleViewProvider {
     @Nullable BubbleExpandedView getExpandedView();
 
-    void setContentVisibility(boolean visible);
+    /**
+     * Sets the alpha of the expanded view content. This will be applied to both the expanded view
+     * container itself (the manage button, etc.) as well as the TaskView within it.
+     */
+    void setExpandedContentAlpha(float alpha);
+
+    /**
+     * Sets whether the contents of the bubble's TaskView should be visible.
+     */
+    void setTaskViewVisibility(boolean visible);
 
     @Nullable View getIconView();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 728f60d..87f0c25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -39,6 +39,7 @@
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
 
@@ -55,6 +56,7 @@
     private final ParentContainerCallbacks mParentContainerCallbacks;
     private Context mContext;
     private SurfaceControlViewHost mViewHost;
+    private SurfaceControl mLeash;
     private boolean mResizingSplits;
     private final String mWindowName;
 
@@ -88,7 +90,15 @@
 
     @Override
     protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
-        mParentContainerCallbacks.attachToParentSurface(b);
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName(TAG)
+                .setHidden(false)
+                .setCallsite("SplitWindowManager#attachToParentSurface");
+        mParentContainerCallbacks.attachToParentSurface(builder);
+        mLeash = builder.build();
+        b.setParent(mLeash);
     }
 
     /** Inflates {@link DividerView} on to the root surface. */
@@ -118,9 +128,15 @@
      * hierarchy.
      */
     void release() {
-        if (mViewHost == null) return;
-        mViewHost.release();
-        mViewHost = null;
+        if (mViewHost != null){
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            new SurfaceControl.Transaction().remove(mLeash).apply();
+            mLeash = null;
+        }
     }
 
     void setResizingSplits(boolean resizing) {
@@ -139,6 +155,6 @@
      */
     @Nullable
     SurfaceControl getSurfaceControl() {
-        return mViewHost == null ? null : getSurfaceControl(mViewHost.getWindowToken());
+        return mLeash;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 3a2f0da..60123ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -31,12 +31,6 @@
 public interface HideDisplayCutout {
     /**
      * Notifies {@link Configuration} changed.
-     * @param newConfig
      */
     void onConfigurationChanged(Configuration newConfig);
-
-    /**
-     * Dumps hide display cutout status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 12b8b87..23f76ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -44,20 +44,12 @@
     @VisibleForTesting
     boolean mEnabled;
 
-    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
-            ShellExecutor mainExecutor) {
-        mContext = context;
-        mOrganizer = organizer;
-        mMainExecutor = mainExecutor;
-        updateStatus();
-    }
-
     /**
      * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
      * supported.
      */
     @Nullable
-    public static HideDisplayCutout create(
+    public static HideDisplayCutoutController create(
             Context context, DisplayController displayController, ShellExecutor mainExecutor) {
         // The SystemProperty is set for devices that support this feature and is used to control
         // whether to create the HideDisplayCutout instance.
@@ -68,7 +60,19 @@
 
         HideDisplayCutoutOrganizer organizer =
                 new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
-        return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl;
+        return new HideDisplayCutoutController(context, organizer, mainExecutor);
+    }
+
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+            ShellExecutor mainExecutor) {
+        mContext = context;
+        mOrganizer = organizer;
+        mMainExecutor = mainExecutor;
+        updateStatus();
+    }
+
+    public HideDisplayCutout asHideDisplayCutout() {
+        return mImpl;
     }
 
     @VisibleForTesting
@@ -94,7 +98,7 @@
         updateStatus();
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String prefix = "  ";
         pw.print(TAG);
         pw.println(" states: ");
@@ -111,14 +115,5 @@
                 HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index bca6deb..d25bef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -115,21 +115,6 @@
     private volatile boolean mAdjustedForIme = false;
     private boolean mHomeStackResizable = false;
 
-    /**
-     * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static LegacySplitScreen create(Context context,
-            DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController imeController, TransactionPool transactionPool,
-            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
-            TaskStackListenerImpl taskStackListener, Transitions transitions,
-            ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
-        return new LegacySplitScreenController(context, displayController, systemWindows,
-                imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener,
-                transitions, mainExecutor, sfVsyncAnimationHandler).mImpl;
-    }
-
     public LegacySplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController imeController, TransactionPool transactionPool,
@@ -228,8 +213,12 @@
                     }
                 });
     }
+    
+    public LegacySplitScreen asLegacySplitScreen() {
+        return mImpl;
+    }
 
-    void onSplitScreenSupported() {
+    public void onSplitScreenSupported() {
         // Set starting tile bounds based on middle target
         final WindowContainerTransaction tct = new WindowContainerTransaction();
         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -237,7 +226,7 @@
         mTaskOrganizer.applyTransaction(tct);
     }
 
-    private void onKeyguardVisibilityChanged(boolean showing) {
+    public void onKeyguardVisibilityChanged(boolean showing) {
         if (!isSplitActive() || mView == null) {
             return;
         }
@@ -293,19 +282,19 @@
         }
     }
 
-    boolean isMinimized() {
+    public boolean isMinimized() {
         return mMinimized;
     }
 
-    boolean isHomeStackResizable() {
+    public boolean isHomeStackResizable() {
         return mHomeStackResizable;
     }
 
-    DividerView getDividerView() {
+    public DividerView getDividerView() {
         return mView;
     }
 
-    boolean isDividerVisible() {
+    public boolean isDividerVisible() {
         return mView != null && mView.getVisibility() == View.VISIBLE;
     }
 
@@ -314,13 +303,13 @@
      * isDividerVisible because the divider is only visible once *everything* is in split mode
      * while this only cares if some things are (eg. while entering/exiting as well).
      */
-    private boolean isSplitActive() {
+    public boolean isSplitActive() {
         return mSplits.mPrimary != null && mSplits.mSecondary != null
                 && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
                 || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
     }
 
-    private void addDivider(Configuration configuration) {
+    public void addDivider(Configuration configuration) {
         Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
         mView = (DividerView)
                 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
@@ -338,14 +327,14 @@
         mWindowManager.add(mView, width, height, mContext.getDisplayId());
     }
 
-    private void removeDivider() {
+    public void removeDivider() {
         if (mView != null) {
             mView.onDividerRemoved();
         }
         mWindowManager.remove();
     }
 
-    private void update(Configuration configuration) {
+    public void update(Configuration configuration) {
         final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
 
         removeDivider();
@@ -358,11 +347,11 @@
         mView.setHidden(isDividerHidden);
     }
 
-    void onTaskVanished() {
+    public void onTaskVanished() {
         removeDivider();
     }
 
-    private void updateVisibility(final boolean visible) {
+    public void updateVisibility(final boolean visible) {
         if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
             mVisible = visible;
@@ -390,7 +379,7 @@
         }
     }
 
-    private void setMinimized(final boolean minimized) {
+    public void setMinimized(final boolean minimized) {
         if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
         mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
@@ -401,7 +390,7 @@
         });
     }
 
-    private void setHomeMinimized(final boolean minimized) {
+    public void setHomeMinimized(final boolean minimized) {
         if (DEBUG) {
             Slog.d(TAG, "setHomeMinimized  min:" + mMinimized + "->" + minimized + " hrsz:"
                     + mHomeStackResizable + " split:" + isDividerVisible());
@@ -441,7 +430,7 @@
         }
     }
 
-    void setAdjustedForIme(boolean adjustedForIme) {
+    public void setAdjustedForIme(boolean adjustedForIme) {
         if (mAdjustedForIme == adjustedForIme) {
             return;
         }
@@ -449,30 +438,30 @@
         updateTouchable();
     }
 
-    private void updateTouchable() {
+    public void updateTouchable() {
         mWindowManager.setTouchable(!mAdjustedForIme);
     }
 
-    private void onUndockingTask() {
+    public void onUndockingTask() {
         if (mView != null) {
             mView.onUndockingTask();
         }
     }
 
-    private void onAppTransitionFinished() {
+    public void onAppTransitionFinished() {
         if (mView == null) {
             return;
         }
         mForcedResizableController.onAppTransitionFinished();
     }
 
-    private void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.print("  mVisible="); pw.println(mVisible);
         pw.print("  mMinimized="); pw.println(mMinimized);
         pw.print("  mAdjustedForIme="); pw.println(mAdjustedForIme);
     }
 
-    long getAnimDuration() {
+    public long getAnimDuration() {
         float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
                 Settings.Global.TRANSITION_ANIMATION_SCALE,
                 mContext.getResources().getFloat(
@@ -482,14 +471,14 @@
         return (long) (transitionDuration * transitionScale);
     }
 
-    void registerInSplitScreenListener(Consumer<Boolean> listener) {
+    public void registerInSplitScreenListener(Consumer<Boolean> listener) {
         listener.accept(isDividerVisible());
         synchronized (mDockedStackExistsListeners) {
             mDockedStackExistsListeners.add(new WeakReference<>(listener));
         }
     }
 
-    void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
+    public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
         synchronized (mDockedStackExistsListeners) {
             for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
                 if (mDockedStackExistsListeners.get(i) == listener) {
@@ -499,13 +488,13 @@
         }
     }
 
-    private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+    public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.add(new WeakReference<>(listener));
         }
     }
 
-    private boolean splitPrimaryTask() {
+    public boolean splitPrimaryTask() {
         try {
             if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
                     || isSplitActive()) {
@@ -538,12 +527,12 @@
                 topRunningTask.taskId, true /* onTop */);
     }
 
-    private void dismissSplitToPrimaryTask() {
+    public void dismissSplitToPrimaryTask() {
         startDismissSplit(true /* toPrimaryTask */);
     }
 
     /** Notifies the bounds of split screen changed. */
-    void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+    public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.removeIf(wf -> {
                 BiConsumer<Rect, Rect> l = wf.get();
@@ -553,19 +542,19 @@
         }
     }
 
-    void startEnterSplit() {
+    public void startEnterSplit() {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         // Set resizable directly here because applyEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
 
-    void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+    public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
         // Set resizable directly here because buildEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
     }
 
-    void finishEnterSplitTransition(boolean minimized) {
+    public void finishEnterSplitTransition(boolean minimized) {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         if (minimized) {
@@ -575,11 +564,11 @@
         }
     }
 
-    void startDismissSplit(boolean toPrimaryTask) {
+    public void startDismissSplit(boolean toPrimaryTask) {
         startDismissSplit(toPrimaryTask, false /* snapped */);
     }
 
-    void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+    public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mSplits.getSplitTransitions().dismissSplit(
                     mSplits, mSplitLayout, !toPrimaryTask, snapped);
@@ -589,7 +578,7 @@
         }
     }
 
-    void onDismissSplit() {
+    public void onDismissSplit() {
         updateVisibility(false /* visible */);
         mMinimized = false;
         // Resets divider bar position to undefined, so new divider bar will apply default position
@@ -599,7 +588,7 @@
         mImePositionProcessor.reset();
     }
 
-    void ensureMinimizedSplit() {
+    public void ensureMinimizedSplit() {
         setHomeMinimized(true /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
@@ -610,7 +599,7 @@
         }
     }
 
-    void ensureNormalSplit() {
+    public void ensureNormalSplit() {
         setHomeMinimized(false /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode, so enter now.
@@ -621,15 +610,15 @@
         }
     }
 
-    LegacySplitDisplayLayout getSplitLayout() {
+    public LegacySplitDisplayLayout getSplitLayout() {
         return mSplitLayout;
     }
 
-    WindowManagerProxy getWmProxy() {
+    public WindowManagerProxy getWmProxy() {
         return mWindowManagerProxy;
     }
 
-    WindowContainerToken getSecondaryRoot() {
+    public WindowContainerToken getSecondaryRoot() {
         if (mSplits == null || mSplits.mSecondary == null) {
             return null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index e958648..11c11f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -69,9 +69,4 @@
      * 3 button navigation mode only
      */
     void registerGestureCallback(OneHandedGestureEventCallback callback);
-
-    /**
-     * Dump one handed status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index eaa704f..5a3c38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -134,10 +134,10 @@
 
 
     /**
-     * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
+     * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
      */
     @Nullable
-    public static OneHanded create(
+    public static OneHandedController create(
             Context context, DisplayController displayController,
             TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
             ShellExecutor mainExecutor, Handler mainHandler) {
@@ -166,7 +166,7 @@
         return new OneHandedController(context, displayController,
                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
                 gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
-                taskStackListener, mainExecutor, mainHandler).mImpl;
+                taskStackListener, mainExecutor, mainHandler);
     }
 
     @VisibleForTesting
@@ -228,6 +228,10 @@
                 mAccessibilityStateChangeListener);
     }
 
+    public OneHanded asOneHanded() {
+        return mImpl;
+    }
+
     /**
      * Set one handed enabled or disabled when user update settings
      */
@@ -468,7 +472,7 @@
         }
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG + "states: ");
         pw.print(innerPrefix + "mOffSetFraction=");
@@ -561,12 +565,5 @@
                 OneHandedController.this.registerGestureCallback(callback);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.dump(pw);
-            });
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ad6f435..3064af6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.transition.Transitions;
 
@@ -131,7 +132,7 @@
     private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Optional<LegacySplitScreen> mSplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -207,7 +208,7 @@
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @NonNull PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional,
+            Optional<LegacySplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -1047,7 +1048,8 @@
     }
 
     /**
-     * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
+     * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+     * screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
@@ -1057,7 +1059,7 @@
             return false;
         }
 
-        LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get();
+        LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
         if (!legacySplitScreen.isDividerVisible()) {
             // fail early if system is not in split screen mode
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
deleted file mode 100644
index 11f22ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage size compat mode UI.
- */
-@ExternalThread
-public interface SizeCompatUI {
-    /**
-     * Called when the Task info changed. Creates and updates the restart button if there is an
-     * activity in size compat, or removes the restart button if there is no size compat activity.
-     *
-     * @param displayId display the task and activity are in.
-     * @param taskId task the activity is in.
-     * @param taskBounds task bounds to place the restart button in.
-     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
-     *                           top activity in this Task is not in size compat.
-     * @param taskListener listener to handle the Task Surface placement.
-     */
-    void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-            @Nullable IBinder sizeCompatActivity,
-            @Nullable ShellTaskOrganizer.TaskListener taskListener);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 286c3b6..48ee86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -48,7 +48,6 @@
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
     @VisibleForTesting
-    final SizeCompatUI mImpl = new SizeCompatUIImpl();
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final DisplayController mDisplayController;
@@ -57,17 +56,8 @@
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
 
-    /** Creates the {@link SizeCompatUIController}. */
-    public static SizeCompatUI create(Context context,
-            DisplayController displayController,
-            DisplayImeController imeController,
-            ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
-                .mImpl;
-    }
-
     @VisibleForTesting
-    SizeCompatUIController(Context context,
+    public SizeCompatUIController(Context context,
             DisplayController displayController,
             DisplayImeController imeController,
             ShellExecutor mainExecutor) {
@@ -79,7 +69,7 @@
         mImeController.addPositionProcessor(this);
     }
 
-    private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+    public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
             @Nullable IBinder sizeCompatActivity,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         // TODO Draw button on Task surface
@@ -177,15 +167,4 @@
         }
         return context;
     }
-
-    private class SizeCompatUIImpl implements SizeCompatUI {
-        @Override
-        public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-                @Nullable IBinder sizeCompatActivity,
-                @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-            mMainExecutor.execute(() ->
-                    SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
-                            taskBounds, sizeCompatActivity, taskListener));
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 177646b..7ca5693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -106,6 +106,8 @@
 
     /** Removes the split-screen stages. */
     void exitSplitScreen();
+    /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide);
     /** Gets the stage bounds. */
     void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index bbad36d..b0167af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -126,6 +126,10 @@
         mStageCoordinator.exitSplitScreen();
     }
 
+    public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+        mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+    }
+
     public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
         mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
     }
@@ -292,6 +296,13 @@
         }
 
         @Override
+        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide);
+            });
+        }
+
+        @Override
         public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
             try {
                 mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 176852b..e44c820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -79,6 +79,7 @@
     private DisplayAreaInfo mDisplayAreaInfo;
     private final Context mContext;
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+    private boolean mExitSplitScreenOnHide = true;
 
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
@@ -113,7 +114,7 @@
     boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
             @SplitScreen.StagePosition int sideStagePosition) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mSideStagePosition = sideStagePosition;
+        setSideStagePosition(sideStagePosition);
         mMainStage.activate(getMainStageBounds(), wct);
         mSideStage.addTask(task, getSideStageBounds(), wct);
         mTaskOrganizer.applyTransaction(wct);
@@ -144,14 +145,18 @@
     }
 
     void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
+        if (mSideStagePosition == sideStagePosition) return;
+
         mSideStagePosition = sideStagePosition;
         if (mSideStageListener.mVisible) {
             onStageVisibilityChanged(mSideStageListener);
         }
+
+        sendOnStagePositionChanged();
     }
 
     void setSideStageVisibility(boolean visible) {
-        if (!mSideStageListener.mVisible == visible) return;
+        if (mSideStageListener.mVisible == visible) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mSideStage.setVisibility(visible, wct);
@@ -162,6 +167,10 @@
         exitSplitScreen(null /* childrenToTop */);
     }
 
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+        mExitSplitScreenOnHide = exitSplitScreenOnHide;
+    }
+
     private void exitSplitScreen(StageTaskListener childrenToTop) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
@@ -202,6 +211,14 @@
         mListeners.remove(listener);
     }
 
+    private void sendOnStagePositionChanged() {
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            final SplitScreen.SplitScreenListener l = mListeners.get(i);
+            l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+            l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+        }
+    }
+
     private void onStageChildTaskStatusChanged(
             StageListenerImpl stageListener, int taskId, boolean present) {
 
@@ -251,7 +268,7 @@
             }
         }
 
-        if (!mainStageVisible && !sideStageVisible) {
+        if (mExitSplitScreenOnHide && !mainStageVisible && !sideStageVisible) {
             // Exit split-screen if both stage are not visible.
             // TODO: This is only a temporary request from UX and is likely to be removed soon...
             exitSplitScreen();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt
new file mode 100644
index 0000000..1869d83
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+@file:JvmName("Utils")
+package com.android.wm.shell.flicker
+
+import android.app.ActivityTaskManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+
+fun removeAllTasksButHome() {
+    val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
+        ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
+        ACTIVITY_TYPE_UNDEFINED)
+    val atm = ActivityTaskManager.getService()
+    atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index 7ec22bb..cac46fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -32,13 +32,12 @@
     /**
      * Opens the IME and wait for it to be displayed
      *
-     * @param device UIDevice instance to interact with the device
      * @param wmHelper Helper used to wait for WindowManager states
      */
     @JvmOverloads
-    open fun openIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
+    open fun openIME(wmHelper: WindowManagerStateHelper? = null) {
         if (!isTelevision) {
-            val editText = device.wait(
+            val editText = uiDevice.wait(
                 Until.findObject(By.res(getPackage(), "plain_text_input")),
                 FIND_TIMEOUT)
 
@@ -47,7 +46,7 @@
                     "was left in an unknown state (e.g. in split screen)"
             }
             editText.click()
-            waitAndAssertIMEShown(device, wmHelper)
+            waitAndAssertIMEShown(uiDevice, wmHelper)
         } else {
             // If we do the same thing as above - editText.click() - on TV, that's going to force TV
             // into the touch mode. We really don't want that.
@@ -69,16 +68,15 @@
     /**
      * Opens the IME and wait for it to be gone
      *
-     * @param device UIDevice instance to interact with the device
      * @param wmHelper Helper used to wait for WindowManager states
      */
     @JvmOverloads
-    open fun closeIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
+    open fun closeIME(wmHelper: WindowManagerStateHelper? = null) {
         if (!isTelevision) {
-            device.pressBack()
+            uiDevice.pressBack()
             // Using only the AccessibilityInfo it is not possible to identify if the IME is active
             if (wmHelper == null) {
-                device.waitForIdle()
+                uiDevice.waitForIdle()
             } else {
                 require(wmHelper.waitImeWindowGone()) { "IME did did not close" }
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index b90e865..111362a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -17,17 +17,20 @@
 package com.android.wm.shell.flicker.helpers
 
 import android.app.Instrumentation
+import android.graphics.Point
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
 import android.os.SystemClock
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
+import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
 import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
 import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
+import com.android.wm.shell.flicker.pip.waitPipWindowGone
+import com.android.wm.shell.flicker.pip.waitPipWindowShown
 import com.android.wm.shell.flicker.testapp.Components
-import org.junit.Assert.fail
 
 class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
     instrumentation,
@@ -55,6 +58,17 @@
         }
     }
 
+    /** {@inheritDoc}  */
+    override fun launchViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        expectedWindowName: String,
+        action: String?,
+        stringExtras: Map<String, String>
+    ) {
+        super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
+        wmHelper.waitPipWindowShown()
+    }
+
     private fun focusOnObject(selector: BySelector): Boolean {
         // We expect all the focusable UI elements to be arranged in a way so that it is possible
         // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
@@ -69,16 +83,12 @@
         return false
     }
 
-    fun clickEnterPipButton() {
+    @JvmOverloads
+    fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) {
         clickObject(ENTER_PIP_BUTTON_ID)
 
-        // TODO(b/172321238): remove this check once hasPipWindow is fixed on TVs
-        if (!isTelevision) {
-            uiDevice.hasPipWindow()
-        } else {
-            // Simply wait for 3 seconds
-            SystemClock.sleep(3_000)
-        }
+        // Wait on WMHelper or simply wait for 3 seconds
+        wmHelper?.waitPipWindowShown() ?: SystemClock.sleep(3_000)
     }
 
     fun clickStartMediaSessionButton() {
@@ -97,16 +107,75 @@
     fun stopMedia() = mediaController?.transportControls?.stop()
             ?: error("No active media session found")
 
+    @Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead",
+        ReplaceWith("closePipWindow(wmHelper)"))
     fun closePipWindow() {
         if (isTelevision) {
             uiDevice.closeTvPipWindow()
         } else {
             uiDevice.closePipWindow()
         }
+    }
 
-        if (!waitUntilClosed()) {
-            fail("Couldn't close Pip")
+    /**
+     * Expands the pip window and dismisses it by clicking on the X button.
+     *
+     * Note, currently the View coordinates reported by the accessibility are relative to
+     * the window, so the correct coordinates need to be calculated
+     *
+     * For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the
+     * dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in
+     * Point(716, 66), instead of Point(970, 1403)
+     *
+     * See b/179337864
+     */
+    fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+        if (isTelevision) {
+            uiDevice.closeTvPipWindow()
+        } else {
+            expandPipWindow(wmHelper)
+            val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"))
+            requireNotNull(exitPipObject) { "PIP window dismiss button not found" }
+            val coordinatesInWindow = exitPipObject.visibleBounds
+            val windowOffset = wmHelper.getWindowRegion(component).bounds
+            val newCoordinates = Point(windowOffset.left + coordinatesInWindow.centerX(),
+                windowOffset.top + coordinatesInWindow.centerY())
+            uiDevice.click(newCoordinates.x, newCoordinates.y)
         }
+
+        // Wait for animation to complete.
+        wmHelper.waitPipWindowGone()
+        wmHelper.waitForHomeActivityVisible()
+    }
+
+    /**
+     * Click once on the PIP window to expand it
+     */
+    fun expandPipWindow(wmHelper: WindowManagerStateHelper) {
+        val windowRegion = wmHelper.getWindowRegion(component)
+        require(!windowRegion.isEmpty) {
+            "Unable to find a PIP window in the current state"
+        }
+        val windowRect = windowRegion.bounds
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        // Ensure WindowManagerService wait until all animations have completed
+        wmHelper.waitForAppTransitionIdle()
+        mInstrumentation.uiAutomation.syncInputTransactions()
+    }
+
+    /**
+     * Double click on the PIP window to reopen to app
+     */
+    fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
+        val windowRegion = wmHelper.getWindowRegion(component)
+        require(!windowRegion.isEmpty) {
+            "Unable to find a PIP window in the current state"
+        }
+        val windowRect = windowRegion.bounds
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        wmHelper.waitPipWindowGone()
+        wmHelper.waitForAppTransitionIdle()
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
index 2015f49..bc42d5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
@@ -16,11 +16,6 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.app.ActivityTaskManager
-import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
-import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
 import android.os.SystemClock
 import com.android.wm.shell.flicker.NonRotationTestBase
 
@@ -29,14 +24,6 @@
     rotation: Int
 ) : NonRotationTestBase(rotationName, rotation) {
     companion object {
-        fun removeAllTasksButHome() {
-            val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
-                    ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
-                    ACTIVITY_TYPE_UNDEFINED)
-            val atm = ActivityTaskManager.getService()
-            atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
-        }
-
         fun waitForAnimationComplete() {
             // TODO: UiDevice doesn't have reliable way to wait for the completion of animation.
             // Consider to introduce WindowManagerStateHelper to access Activity state.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index 5a3d18d..a14b46e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -16,21 +16,21 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Presubmit
+import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runFlicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,82 +39,67 @@
  * Test Pip launch and exit.
  * To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
  */
-@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class EnterExitPipTest(
-    rotationName: String,
-    rotation: Int
-) : AppTestBase(rotationName, rotation) {
-    private val pipApp = PipAppHelper(instrumentation)
-    private val testApp = FixedAppHelper(instrumentation)
-
-    @Test
-    fun testDisplayMetricsPinUnpin() {
-        runFlicker(instrumentation) {
-            withTestName { "testDisplayMetricsPinUnpin" }
-            setup {
-                test {
-                    removeAllTasksButHome()
-                    device.wakeUpAndGoToHomeScreen()
-                    pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
-                    testApp.launchViaIntent()
-                    waitForAnimationComplete()
-                }
-            }
-            transitions {
-                // This will bring PipApp to fullscreen
-                pipApp.launchViaIntent()
-                waitForAnimationComplete()
-            }
-            teardown {
-                test {
-                    removeAllTasksButHome()
-                }
-            }
-            assertions {
-                val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                windowManagerTrace {
-                    all("pipApp must remain inside visible bounds") {
-                        coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
-                    }
-                    all("Initially shows both app windows then pipApp hides testApp") {
-                        showsAppWindow(testApp.defaultWindowName)
-                                .showsAppWindowOnTop(pipApp.defaultWindowName)
-                                .then()
-                                .hidesAppWindow(testApp.defaultWindowName)
-                    }
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-                layersTrace {
-                    all("Initially shows both app layers then pipApp hides testApp") {
-                        showsLayer(testApp.defaultWindowName)
-                                .showsLayer(pipApp.defaultWindowName)
-                                .then()
-                                .hidesLayer(testApp.defaultWindowName)
-                    }
-                    start("testApp covers the fullscreen, pipApp remains inside display") {
-                        hasVisibleRegion(testApp.defaultWindowName, displayBounds)
-                        coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
-                    }
-                    end("pipApp covers the fullscreen") {
-                        hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
-                    }
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                }
-            }
-        }
-    }
-
-    companion object {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        fun getParams(): List<Array<Any>> {
+            val testApp = FixedAppHelper(instrumentation)
+            val baseConfig = getTransitionLaunch(eachRun = true)
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                setup {
+                    eachRun {
+                        testApp.launchViaIntent(wmHelper)
+                    }
+                }
+                transitions {
+                    // This will bring PipApp to fullscreen
+                    pipApp.launchViaIntent(wmHelper)
+                }
+                assertions {
+                    val displayBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
+                    presubmit {
+                        windowManagerTrace {
+                            all("pipApp must remain inside visible bounds") {
+                                coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
+                            }
+                            all("Initially shows both app windows then pipApp hides testApp") {
+                                showsAppWindow(testApp.defaultWindowName)
+                                    .showsAppWindowOnTop(pipApp.defaultWindowName)
+                                    .then()
+                                    .hidesAppWindow(testApp.defaultWindowName)
+                            }
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                        }
+                        layersTrace {
+                            all("Initially shows both app layers then pipApp hides testApp") {
+                                showsLayer(testApp.defaultWindowName)
+                                    .showsLayer(pipApp.defaultWindowName)
+                                    .then()
+                                    .hidesLayer(testApp.defaultWindowName)
+                            }
+                            start("testApp covers the fullscreen, pipApp remains inside display") {
+                                hasVisibleRegion(testApp.defaultWindowName, displayBounds)
+                                coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
+                            }
+                            end("pipApp covers the fullscreen") {
+                                hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
+                            }
+                            navBarLayerIsAlwaysVisible()
+                            statusBarLayerIsAlwaysVisible()
+                        }
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+                repetitions = 5)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index af62eb9..99a40da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,18 +16,13 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.os.Bundle
 import android.view.Surface
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.expandPipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -35,9 +30,7 @@
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.startRotation
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -50,80 +43,58 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
 class EnterPipTest(
     testSpec: FlickerTestRunnerFactory.TestSpec
 ) : FlickerTestRunner(testSpec) {
-    companion object {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
-                    withTestName { buildTestTag("enterPip", testApp, configuration) }
-                    repeat { configuration.repetitions }
-                    setup {
-                        test {
-                            device.wakeUpAndGoToHomeScreen()
-                        }
-                        eachRun {
-                            device.pressHome()
-                            testApp.launchViaIntent(wmHelper)
-                            this.setRotation(configuration.startRotation)
-                        }
-                    }
-                    teardown {
-                        eachRun {
-                            if (device.hasPipWindow()) {
-                                device.closePipWindow()
-                            }
-                            testApp.exit()
-                            this.setRotation(Surface.ROTATION_0)
-                        }
-                        test {
-                            if (device.hasPipWindow()) {
-                                device.closePipWindow()
-                            }
-                        }
-                    }
-                    transitions {
-                        testApp.clickEnterPipButton()
-                        device.expandPipWindow()
-                    }
-                    assertions {
+            val baseConfig = getTransitionLaunch(
+                eachRun = true, stringExtras = emptyMap())
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                transitions {
+                    pipApp.clickEnterPipButton()
+                    pipApp.expandPipWindow(wmHelper)
+                }
+                assertions {
+                    presubmit {
                         windowManagerTrace {
                             navBarWindowIsAlwaysVisible()
                             statusBarWindowIsAlwaysVisible()
 
                             all("pipWindowBecomesVisible") {
-                                this.showsAppWindow(testApp.`package`)
-                                    .then()
-                                    .showsAppWindow(PIP_WINDOW_TITLE)
+                                this.showsAppWindow(pipApp.defaultWindowName)
                             }
                         }
 
                         layersTrace {
-                            navBarLayerIsAlwaysVisible(bugId = 140855415)
                             statusBarLayerIsAlwaysVisible()
-                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
-                                enabled = false)
-                            navBarLayerRotatesAndScales(configuration.startRotation,
-                                Surface.ROTATION_0, bugId = 140855415)
                             statusBarLayerRotatesScales(configuration.startRotation,
                                 Surface.ROTATION_0)
                         }
 
                         layersTrace {
                             all("pipLayerBecomesVisible") {
-                                this.showsLayer(testApp.launcherName)
-                                    .then()
-                                    .showsLayer(PIP_WINDOW_TITLE)
+                                this.showsLayer(pipApp.launcherName)
                             }
                         }
                     }
+
+                    flaky {
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                        }
+                    }
                 }
+            }
+
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+                repetitions = 5)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
new file mode 100644
index 0000000..eaaa2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
+import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterPipToOtherOrientationTest(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
+        private val testApp = FixedAppHelper(instrumentation)
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0),
+                repetitions = 5) { configuration ->
+                setupAndTeardown(this, configuration)
+
+                setup {
+                    eachRun {
+                        // Launch a portrait only app on the fullscreen stack
+                        testApp.launchViaIntent(wmHelper, stringExtras = mapOf(
+                            EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
+                        // Launch the PiP activity fixed as landscape
+                        pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
+                            EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+                    }
+                }
+                teardown {
+                    eachRun {
+                        pipApp.exit()
+                        testApp.exit()
+                    }
+                }
+                transitions {
+                    // Enter PiP, and assert that the PiP is within bounds now that the device is back
+                    // in portrait
+                    broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
+                    wmHelper.waitPipWindowShown()
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+                    val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+
+                    presubmit {
+                        windowManagerTrace {
+                            all("pipApp window is always on top") {
+                                showsAppWindowOnTop(pipApp.defaultWindowName)
+                            }
+                            start("pipApp window hides testApp") {
+                                isInvisible(testApp.defaultWindowName)
+                            }
+                            end("testApp windows is shown") {
+                                isVisible(testApp.defaultWindowName)
+                            }
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                        }
+
+                        layersTrace {
+                            start("pipApp layer hides testApp") {
+                                hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
+                                isInvisible(testApp.defaultWindowName)
+                            }
+                        }
+                    }
+
+                    flaky {
+                        layersTrace {
+                            end("testApp layer covers fullscreen") {
+                                hasVisibleRegion(testApp.defaultWindowName, endingBounds)
+                            }
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
new file mode 100644
index 0000000..707d28d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.pip
+
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.parser.toWindowName
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.google.common.truth.Truth
+
+inline val WindowManagerState.pinnedWindows
+    get() = visibleWindows
+        .filter { it.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED }
+
+/**
+ * Checks if the state has any window in PIP mode
+ */
+fun WindowManagerState.hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
+
+/**
+ * Checks that an activity [activity] is in PIP mode
+ */
+fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean {
+    val windowName = activity.toWindowName()
+    return pinnedWindows.any { it.title == windowName }
+}
+
+/**
+ * Asserts that an activity [activity] exists and is in PIP mode
+ */
+fun WindowManagerStateSubject.isInPipMode(
+    activity: ComponentName
+): WindowManagerStateSubject = apply {
+    val windowName = activity.toWindowName()
+    hasWindow(windowName)
+    val pinnedWindows = wmState.pinnedWindows
+        .map { it.title }
+    Truth.assertWithMessage("Window not in PIP mode")
+        .that(pinnedWindows)
+        .contains(windowName)
+}
+
+/**
+ * Waits until the state has a window in PIP mode, i.e., with
+ * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
+ */
+fun WindowManagerStateHelper.waitPipWindowShown(): Boolean =
+    waitFor("PIP window shown") {
+        val result = it.wmState.hasPipWindow()
+        result
+    }
+
+/**
+ * Waits until the state doesn't have a window in PIP mode, i.e., with
+ * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
+ */
+fun WindowManagerStateHelper.waitPipWindowGone(): Boolean =
+    waitFor("PIP window gone") {
+        val result = !it.wmState.hasPipWindow()
+        result
+    }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index c21b594..7576e24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,21 +16,19 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Presubmit
+import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.startRotation
 import com.android.wm.shell.flicker.IME_WINDOW_NAME
 import com.android.wm.shell.flicker.helpers.ImeAppHelper
-import com.android.wm.shell.flicker.testapp.Components
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,114 +37,61 @@
  * Test Pip launch.
  * To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
  */
-@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipKeyboardTest(
-    rotationName: String,
-    rotation: Int
-) : PipTestBase(rotationName, rotation) {
-    private val keyboardApp = ImeAppHelper(instrumentation)
-    private val keyboardComponent = Components.ImeActivity.COMPONENT
-    private val helper = WindowManagerStateHelper()
-
-    private val keyboardScenario: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            repeat { TEST_REPETITIONS }
-            // disable layer tracing
-            withLayerTracing { null }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    device.pressHome()
-                    // launch our target pip app
-                    testApp.launchViaIntent(wmHelper)
-                    this.setRotation(rotation)
-                    testApp.clickEnterPipButton()
-                    // open an app with an input field and a keyboard
-                    // UiAutomator doesn't support to launch the multiple Activities in a task.
-                    // So use launchActivity() for the Keyboard Activity.
-                    keyboardApp.launchViaIntent()
-                    helper.waitForAppTransitionIdle()
-                    helper.waitForFullScreenApp(keyboardComponent)
-                }
-            }
-            teardown {
-                test {
-                    keyboardApp.exit()
-
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-        }
-
-    /** Ensure the pip window remains visible throughout any keyboard interactions. */
-    @Test
-    fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() {
-        val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose"
-        runWithFlicker(keyboardScenario) {
-            withTestName { testTag }
-            transitions {
-                // open the soft keyboard
-                keyboardApp.openIME(device, wmHelper)
-                helper.waitImeWindowShown()
-
-                // then close it again
-                keyboardApp.closeIME(device, wmHelper)
-                helper.waitImeWindowGone()
-            }
-            assertions {
-                windowManagerTrace {
-                    all("PiP window must remain inside visible bounds") {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        coversAtMostRegion(testApp.defaultWindowName, displayBounds)
-                    }
-                }
-            }
-        }
-    }
-
-    /** Ensure the pip window does not obscure the keyboard. */
-    @Test
-    fun pipWindow_doesNotObscure_keyboard() {
-        val testTag = "pipWindow_doesNotObscure_keyboard"
-        runWithFlicker(keyboardScenario) {
-            withTestName { testTag }
-            transitions {
-                // open the soft keyboard
-                keyboardApp.openIME(device, wmHelper)
-                helper.waitImeWindowShown()
-            }
-            teardown {
-                eachRun {
-                    // close the keyboard
-                    keyboardApp.closeIME(device, wmHelper)
-                    helper.waitImeWindowGone()
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    end("imeWindowAboveApp") {
-                        isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        private const val TEST_REPETITIONS = 5
+class PipKeyboardTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTestRunner(testSpec) {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
+        private const val TAG_IME_VISIBLE = "imeIsVisible"
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+            val imeApp = ImeAppHelper(instrumentation)
+            val baseConfig = getTransitionLaunch(eachRun = false)
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                setup {
+                    test {
+                        imeApp.launchViaIntent(wmHelper)
+                        setRotation(configuration.startRotation)
+                    }
+                }
+                teardown {
+                    test {
+                        imeApp.exit()
+                        setRotation(Surface.ROTATION_0)
+                    }
+                }
+                transitions {
+                    // open the soft keyboard
+                    imeApp.openIME(wmHelper)
+                    createTag(TAG_IME_VISIBLE)
+
+                    // then close it again
+                    imeApp.closeIME(wmHelper)
+                }
+                assertions {
+                    presubmit {
+                        windowManagerTrace {
+                            // Ensure the pip window remains visible throughout
+                            // any keyboard interactions
+                            all("pipInVisibleBounds") {
+                                val displayBounds = WindowUtils.getDisplayBounds(
+                                    configuration.startRotation)
+                                coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
+                            }
+                            // Ensure that the pip window does not obscure the keyboard
+                            tag(TAG_IME_VISIBLE) {
+                                isAboveWindow(IME_WINDOW_NAME, pipApp.defaultWindowName)
+                            }
+                        }
+                    }
+                }
+            }
+
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+                repetitions = 5)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index e579096..f10bd7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -32,6 +32,7 @@
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.removeAllTasksButHome
 import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
deleted file mode 100644
index 5e0760c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.pip
-
-import android.content.Intent
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import org.junit.Assert.assertEquals
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipOrientationTest(
-    rotationName: String,
-    rotation: Int
-) : AppTestBase(rotationName, rotation) {
-    // Helper class to process test actions by broadcast.
-    private inner class BroadcastActionTrigger {
-        private fun createIntentWithAction(broadcastAction: String): Intent {
-            return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-        }
-        fun doAction(broadcastAction: String) {
-            instrumentation.getContext().sendBroadcast(createIntentWithAction(broadcastAction))
-        }
-        fun requestOrientationForPip(orientation: Int) {
-            instrumentation.getContext()
-                    .sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
-                    .putExtra(EXTRA_PIP_ORIENTATION, orientation.toString()))
-        }
-    }
-    private val broadcastActionTrigger = BroadcastActionTrigger()
-
-    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-    private val ORIENTATION_LANDSCAPE = 0
-    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-    private val ORIENTATION_PORTRAIT = 1
-
-    private val testApp = FixedAppHelper(instrumentation)
-    private val pipApp = PipAppHelper(instrumentation)
-
-    @Test
-    fun testEnterPipToOtherOrientation() {
-        runFlicker(instrumentation) {
-            withTestName { "testEnterPipToOtherOrientation" }
-            setup {
-                test {
-                    removeAllTasksButHome()
-                    device.wakeUpAndGoToHomeScreen()
-                    // Launch a portrait only app on the fullscreen stack
-                    testApp.launchViaIntent(stringExtras = mapOf(
-                            EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
-                    waitForAnimationComplete()
-                    // Launch the PiP activity fixed as landscape
-                    pipApp.launchViaIntent(stringExtras = mapOf(
-                            EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
-                    waitForAnimationComplete()
-                }
-            }
-            transitions {
-                // Enter PiP, and assert that the PiP is within bounds now that the device is back
-                // in portrait
-                broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
-                waitForAnimationComplete()
-            }
-            teardown {
-                test {
-                    removeAllTasksButHome()
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    all("pipApp window is always on top") {
-                        showsAppWindowOnTop(pipApp.defaultWindowName)
-                    }
-                    start("pipApp window hides testApp") {
-                        isInvisible(testApp.defaultWindowName)
-                    }
-                    end("testApp windows is shown") {
-                        isVisible(testApp.defaultWindowName)
-                    }
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-                layersTrace {
-                    val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
-                    val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
-                    start("pipApp layer hides testApp") {
-                        hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
-                        isInvisible(testApp.defaultWindowName)
-                    }
-                    end("testApp layer covers fullscreen", enabled = false) {
-                        hasVisibleRegion(testApp.defaultWindowName, endingBounds)
-                    }
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testSetRequestedOrientationWhilePinned() {
-        runFlicker(instrumentation) {
-            withTestName { "testSetRequestedOrientationWhilePinned" }
-            setup {
-                test {
-                    removeAllTasksButHome()
-                    device.wakeUpAndGoToHomeScreen()
-                    // Launch the PiP activity fixed as landscape
-                    pipApp.launchViaIntent(stringExtras = mapOf(
-                            EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
-                            EXTRA_ENTER_PIP to "true"))
-                    waitForAnimationComplete()
-                    assertEquals(Surface.ROTATION_0, device.displayRotation)
-                }
-            }
-            transitions {
-                // Request that the orientation is set to landscape
-                broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
-
-                // Launch the activity back into fullscreen and ensure that it is now in landscape
-                pipApp.launchViaIntent()
-                waitForAnimationComplete()
-                assertEquals(Surface.ROTATION_90, device.displayRotation)
-            }
-            teardown {
-                test {
-                    removeAllTasksButHome()
-                }
-            }
-            assertions {
-                val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
-                val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
-                windowManagerTrace {
-                    start("PIP window must remain inside display") {
-                        coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
-                    }
-                    end("pipApp shows on top") {
-                        showsAppWindowOnTop(pipApp.defaultWindowName)
-                    }
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-                layersTrace {
-                    start("PIP layer must remain inside display") {
-                        coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
-                    }
-                    end("pipApp layer covers fullscreen") {
-                        hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
-                    }
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index a00c5f4..adab5e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -16,21 +16,18 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Presubmit
+import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.startRotation
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -38,7 +35,6 @@
 import com.android.server.wm.flicker.noUncoveredRegions
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -48,80 +44,77 @@
  * Test Pip Stack in bounds after rotations.
  * To run this test: `atest WMShellFlickerTests:PipRotationTest`
  */
-@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class PipRotationTest(
     testSpec: FlickerTestRunnerFactory.TestSpec
 ) : FlickerTestRunner(testSpec) {
-    companion object {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = FixedAppHelper(instrumentation)
-            val pipApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
-                configuration ->
-                        withTestName { buildTestTag("PipRotationTest", testApp, configuration) }
-                        repeat { configuration.repetitions }
-                        setup {
-                            test {
-                                AppTestBase.removeAllTasksButHome()
-                                device.wakeUpAndGoToHomeScreen()
-                                pipApp.launchViaIntent(stringExtras = mapOf(
-                                    EXTRA_ENTER_PIP to "true"))
-                                testApp.launchViaIntent()
-                                AppTestBase.waitForAnimationComplete()
-                            }
-                            eachRun {
-                                setRotation(configuration.startRotation)
-                            }
+            val fixedApp = FixedAppHelper(instrumentation)
+            val baseConfig = getTransitionLaunch(eachRun = false)
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                setup {
+                    test {
+                        fixedApp.launchViaIntent(wmHelper)
+                    }
+                    eachRun {
+                        setRotation(configuration.startRotation)
+                    }
+                }
+                transitions {
+                    setRotation(configuration.endRotation)
+                }
+                teardown {
+                    eachRun {
+                        setRotation(Surface.ROTATION_0)
+                    }
+                }
+                assertions {
+                    val startingBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
+                    val endingBounds = WindowUtils.getDisplayBounds(configuration.endRotation)
+
+                    presubmit {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
                         }
-                        transitions {
-                            setRotation(configuration.endRotation)
+
+                        layersTrace {
+                            noUncoveredRegions(configuration.startRotation,
+                                configuration.endRotation, allStates = false)
                         }
-                        teardown {
-                            eachRun {
-                                setRotation(Surface.ROTATION_0)
+                    }
+
+                    flaky {
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                configuration.endRotation, bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                configuration.endRotation, bugId = 140855415)
+
+                            start("appLayerRotates_StartingBounds", bugId = 140855415) {
+                                hasVisibleRegion(fixedApp.defaultWindowName, startingBounds)
+                                coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
                             }
-                            test {
-                                AppTestBase.removeAllTasksButHome()
-                            }
-                        }
-                        assertions {
-                            windowManagerTrace {
-                                navBarWindowIsAlwaysVisible()
-                                statusBarWindowIsAlwaysVisible()
-                            }
-                            layersTrace {
-                                navBarLayerIsAlwaysVisible(bugId = 140855415)
-                                statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                                noUncoveredRegions(configuration.startRotation,
-                                    configuration.endRotation, allStates = false)
-                                navBarLayerRotatesAndScales(configuration.startRotation,
-                                    configuration.endRotation, bugId = 140855415)
-                                statusBarLayerRotatesScales(configuration.startRotation,
-                                    configuration.endRotation, bugId = 140855415)
-                            }
-                            layersTrace {
-                                val startingBounds = WindowUtils.getDisplayBounds(
-                                    configuration.startRotation)
-                                val endingBounds = WindowUtils.getDisplayBounds(
-                                    configuration.endRotation)
-                                start("appLayerRotates_StartingBounds", bugId = 140855415) {
-                                    hasVisibleRegion(testApp.defaultWindowName, startingBounds)
-                                    coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
-                                }
-                                end("appLayerRotates_EndingBounds", bugId = 140855415) {
-                                    hasVisibleRegion(testApp.defaultWindowName, endingBounds)
-                                    coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
-                                }
+                            end("appLayerRotates_EndingBounds", bugId = 140855415) {
+                                hasVisibleRegion(fixedApp.defaultWindowName, endingBounds)
+                                coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
                             }
                         }
                     }
+                }
+            }
+
+            return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
+                baseConfig, testSpec,
+                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+                repetitions = 5)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 3e7eb134..4b826ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -16,29 +16,23 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.os.Bundle
 import android.view.Surface
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.expandPipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -51,48 +45,30 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
 class PipToAppTest(
     testSpec: FlickerTestRunnerFactory.TestSpec
 ) : FlickerTestRunner(testSpec) {
-    companion object {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
-                    withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
-                    repeat { configuration.repetitions }
-                    setup {
-                        test {
-                            device.wakeUpAndGoToHomeScreen()
-                            device.pressHome()
-                            testApp.launchViaIntent(wmHelper)
-                        }
-                        eachRun {
-                            this.setRotation(configuration.startRotation)
-                            testApp.clickEnterPipButton()
-                            device.hasPipWindow()
-                        }
+            val baseConfig = getTransitionLaunch(eachRun = true)
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                setup {
+                    eachRun {
+                        this.setRotation(configuration.startRotation)
                     }
-                    teardown {
-                        eachRun {
-                            this.setRotation(Surface.ROTATION_0)
-                        }
-                        test {
-                            if (device.hasPipWindow()) {
-                                device.closePipWindow()
-                            }
-                            testApp.exit()
-                        }
+                }
+                teardown {
+                    eachRun {
+                        this.setRotation(Surface.ROTATION_0)
                     }
-                    transitions {
-                        device.expandPipWindow()
-                        device.waitForIdle()
-                    }
-                    assertions {
+                }
+                transitions {
+                    pipApp.expandPipWindowToApp(wmHelper)
+                }
+                assertions {
+                    presubmit {
                         windowManagerTrace {
                             navBarWindowIsAlwaysVisible()
                             statusBarWindowIsAlwaysVisible()
@@ -100,34 +76,42 @@
                             all("appReplacesPipWindow") {
                                 this.showsAppWindow(PIP_WINDOW_TITLE)
                                     .then()
-                                    .showsAppWindowOnTop(testApp.launcherName)
+                                    .showsAppWindowOnTop(pipApp.launcherName)
                             }
                         }
 
                         layersTrace {
-                            navBarLayerIsAlwaysVisible(bugId = 140855415)
                             statusBarLayerIsAlwaysVisible()
-                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
-                                enabled = false)
-                            navBarLayerRotatesAndScales(configuration.startRotation,
-                                Surface.ROTATION_0, bugId = 140855415)
                             statusBarLayerRotatesScales(configuration.startRotation,
                                 Surface.ROTATION_0)
 
                             all("appReplacesPipLayer") {
                                 this.showsLayer(PIP_WINDOW_TITLE)
                                     .then()
-                                    .showsLayer(testApp.launcherName)
+                                    .showsLayer(pipApp.launcherName)
                             }
                         }
+                    }
+
+                    flaky {
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                        }
 
                         eventLog {
                             focusChanges(
-                                "NexusLauncherActivity", testApp.launcherName,
+                                "NexusLauncherActivity", pipApp.launcherName,
                                 "NexusLauncherActivity", bugId = 151179149)
                         }
                     }
                 }
+            }
+
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index 5d3bc13..62e8221 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -16,28 +16,23 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.os.Bundle
 import android.view.Surface
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -50,50 +45,30 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
 class PipToHomeTest(
     testSpec: FlickerTestRunnerFactory.TestSpec
 ) : FlickerTestRunner(testSpec) {
-    companion object {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
-                    withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
-                    repeat { configuration.repetitions }
-                    setup {
-                        test {
-                            device.wakeUpAndGoToHomeScreen()
-                            device.pressHome()
-                        }
-                        eachRun {
-                            testApp.launchViaIntent(wmHelper)
-                            this.setRotation(configuration.startRotation)
-                            testApp.clickEnterPipButton()
-                            device.hasPipWindow()
-                        }
+            val baseConfig = getTransitionLaunch(eachRun = true)
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                setup {
+                    eachRun {
+                        this.setRotation(configuration.startRotation)
                     }
-                    teardown {
-                        eachRun {
-                            this.setRotation(Surface.ROTATION_0)
-                            if (device.hasPipWindow()) {
-                                device.closePipWindow()
-                            }
-                        }
-                        test {
-                            if (device.hasPipWindow()) {
-                                device.closePipWindow()
-                            }
-                            testApp.exit()
-                        }
+                }
+                teardown {
+                    eachRun {
+                        this.setRotation(Surface.ROTATION_0)
                     }
-                    transitions {
-                        testApp.closePipWindow()
-                    }
-                    assertions {
+                }
+                transitions {
+                    pipApp.closePipWindow(wmHelper)
+                }
+                assertions {
+                    presubmit {
                         windowManagerTrace {
                             navBarWindowIsAlwaysVisible()
                             statusBarWindowIsAlwaysVisible()
@@ -106,12 +81,7 @@
                         }
 
                         layersTrace {
-                            navBarLayerIsAlwaysVisible(bugId = 140855415)
                             statusBarLayerIsAlwaysVisible()
-                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
-                                enabled = false)
-                            navBarLayerRotatesAndScales(configuration.startRotation,
-                                Surface.ROTATION_0, bugId = 140855415)
                             statusBarLayerRotatesScales(configuration.startRotation,
                                 Surface.ROTATION_0)
 
@@ -121,13 +91,28 @@
                                     .hidesLayer(PIP_WINDOW_TITLE)
                             }
                         }
+                    }
 
+                    postsubmit {
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0)
+                        }
+                    }
+
+                    flaky {
                         eventLog {
-                            focusChanges(testApp.launcherName, "NexusLauncherActivity",
+                            focusChanges(pipApp.launcherName, "NexusLauncherActivity",
                                 bugId = 151179149)
                         }
                     }
                 }
+            }
+
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
new file mode 100644
index 0000000..eb7bae1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.pip
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.os.Bundle
+import android.view.Surface
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.closePipWindow
+import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.repetitions
+import com.android.wm.shell.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.removeAllTasksButHome
+import com.android.wm.shell.flicker.testapp.Components
+
+abstract class PipTransitionBase(protected val instrumentation: Instrumentation) {
+    // Helper class to process test actions by broadcast.
+    protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
+        private fun createIntentWithAction(broadcastAction: String): Intent {
+            return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+        }
+
+        fun doAction(broadcastAction: String) {
+            instrumentation.context
+                .sendBroadcast(createIntentWithAction(broadcastAction))
+        }
+
+        fun requestOrientationForPip(orientation: Int) {
+            instrumentation.context.sendBroadcast(
+                    createIntentWithAction(Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION)
+                    .putExtra(Components.PipActivity.EXTRA_PIP_ORIENTATION, orientation.toString())
+            )
+        }
+
+        companion object {
+            // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+            @JvmStatic
+            val ORIENTATION_LANDSCAPE = 0
+
+            // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+            @JvmStatic
+            val ORIENTATION_PORTRAIT = 1
+        }
+    }
+
+    protected val pipApp = PipAppHelper(instrumentation)
+    protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
+
+    /**
+     * Gets a configuration that handles basic setup and teardown of pip tests
+     */
+    protected val setupAndTeardown: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            withTestName { buildTestTag(configuration) }
+            repeat { configuration.repetitions }
+            setup {
+                test {
+                    removeAllTasksButHome()
+                    device.wakeUpAndGoToHomeScreen()
+                }
+            }
+            teardown {
+                eachRun {
+                    setRotation(Surface.ROTATION_0)
+                }
+                test {
+                    removeAllTasksButHome()
+
+                    if (device.hasPipWindow()) {
+                        device.closePipWindow()
+                    }
+                    pipApp.exit()
+                }
+            }
+        }
+
+    /**
+     * Gets a configuration that handles basic setup and teardown of pip tests and that
+     * launches the Pip app for test
+     *
+     * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
+     * @param stringExtras Arguments to pass to the PIP launch intent
+     */
+    @JvmOverloads
+    fun getTransitionLaunch(
+        eachRun: Boolean,
+        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+    ): FlickerBuilder.(Bundle) -> Unit {
+        return { configuration ->
+            setupAndTeardown(this, configuration)
+
+            setup {
+                test {
+                    removeAllTasksButHome()
+                    if (!eachRun) {
+                        pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+                        wmHelper.waitPipWindowShown()
+                    }
+                }
+                eachRun {
+                    if (eachRun) {
+                        pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+                        wmHelper.waitPipWindowShown()
+                    }
+                }
+            }
+            teardown {
+                eachRun {
+                    if (eachRun) {
+                        pipApp.exit()
+                    }
+                }
+                test {
+                    if (!eachRun) {
+                        pipApp.exit()
+                    }
+                    removeAllTasksButHome()
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
new file mode 100644
index 0000000..c01bc94
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.Assert.assertEquals
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SetRequestedOrientationWhilePinnedTest(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0),
+                repetitions = 1) { configuration ->
+                setupAndTeardown(this, configuration)
+
+                setup {
+                    eachRun {
+                        // Launch the PiP activity fixed as landscape
+                        pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
+                            EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
+                            EXTRA_ENTER_PIP to "true"))
+                    }
+                }
+                teardown {
+                    eachRun {
+                        pipApp.exit()
+                    }
+                }
+                transitions {
+                    // Request that the orientation is set to landscape
+                    broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
+
+                    // Launch the activity back into fullscreen and
+                    // ensure that it is now in landscape
+                    pipApp.launchViaIntent(wmHelper)
+                    wmHelper.waitForFullScreenApp(pipApp.component)
+                    wmHelper.waitForRotation(Surface.ROTATION_90)
+                    assertEquals(Surface.ROTATION_90, device.displayRotation)
+                }
+                assertions {
+                    val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+                    val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+                    presubmit {
+                        windowManagerTrace {
+                            start("PIP window must remain inside display") {
+                                coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
+                            }
+                            end("pipApp shows on top") {
+                                showsAppWindowOnTop(pipApp.defaultWindowName)
+                            }
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                        }
+                        layersTrace {
+                            start("PIP layer must remain inside display") {
+                                coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
+                            }
+                            end("pipApp layer covers fullscreen") {
+                                hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
+                            }
+                        }
+                    }
+
+                    flaky {
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 80ea9b9..176b33d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -51,7 +51,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,7 +76,7 @@
     @Mock
     private Context mContext;
     @Mock
-    private SizeCompatUI mSizeCompatUI;
+    private SizeCompatUIController mSizeCompatUI;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9430af9..d10c036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 
 import org.junit.Before;
@@ -70,7 +71,7 @@
     @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
-    @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
+    @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 98f01ff..0eb64e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -86,7 +86,7 @@
         final Rect taskBounds = new Rect(0, 0, 1000, 2000);
 
         // Verify that the restart button is added with non-null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
                 mMockActivityToken, mMockTaskListener);
         mShellMainExecutor.flushAll();
 
@@ -94,7 +94,7 @@
         verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
 
         // Verify that the restart button is removed with null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
 
         mShellMainExecutor.flushAll();
         verify(mMockButton).remove();
@@ -104,7 +104,7 @@
     public void testChangeButtonVisibilityOnImeShowHide() {
         final int taskId = 12;
         final Rect taskBounds = new Rect(0, 0, 1000, 2000);
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
                 mMockActivityToken, mMockTaskListener);
         mShellMainExecutor.flushAll();
 
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index ca5981c0..9c743ce 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -83,8 +83,16 @@
     return {};
   }
 
+  std::unique_ptr<AssetsProvider> overlay_assets;
   const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  auto overlay_assets = ZipAssetsProvider::Create(overlay_path);
+  if (IsFabricatedOverlay(overlay_path)) {
+    // Fabricated overlays do not contain resource definitions. All of the overlay resource values
+    // are defined inline in the idmap.
+    overlay_assets = EmptyAssetsProvider::Create();
+  } else {
+    // The overlay should be an APK.
+    overlay_assets = ZipAssetsProvider::Create(overlay_path);
+  }
   if (overlay_assets == nullptr) {
     return {};
   }
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 03ab62f..36bde5c 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -102,9 +102,8 @@
   memset(&configuration_, 0, sizeof(configuration_));
 }
 
-bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
-                                 bool invalidate_caches) {
-  apk_assets_ = apk_assets;
+bool AssetManager2::SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches) {
+  apk_assets_ = std::move(apk_assets);
   BuildDynamicRefTable();
   RebuildFilterList();
   if (invalidate_caches) {
@@ -137,6 +136,36 @@
   // 0x01 is reserved for the android package.
   int next_package_id = 0x02;
   for (const ApkAssets* apk_assets : sorted_apk_assets) {
+    std::shared_ptr<OverlayDynamicRefTable> overlay_ref_table;
+    if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
+      // The target package must precede the overlay package in the apk assets paths in order
+      // to take effect.
+      auto iter = apk_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      if (iter == apk_assets_package_ids.end()) {
+         LOG(INFO) << "failed to find target package for overlay "
+                   << loaded_idmap->OverlayApkPath();
+      } else {
+        uint8_t target_package_id = iter->second;
+
+        // Create a special dynamic reference table for the overlay to rewrite references to
+        // overlay resources as references to the target resources they overlay.
+        overlay_ref_table = std::make_shared<OverlayDynamicRefTable>(
+            loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
+
+        // Add the overlay resource map to the target package's set of overlays.
+        const uint8_t target_idx = package_ids_[target_package_id];
+        CHECK(target_idx != 0xff) << "overlay target '" << loaded_idmap->TargetApkPath()
+                                  << "'added to apk_assets_package_ids but does not have an"
+                                  << " assigned package group";
+
+        PackageGroup& target_package_group = package_groups_[target_idx];
+        target_package_group.overlays_.push_back(
+            ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
+                                                                  overlay_ref_table.get()),
+                              apk_assets_cookies[apk_assets]});
+      }
+    }
+
     const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
     for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
       // Get the package ID or assign one if a shared library.
@@ -147,50 +176,25 @@
         package_id = package->GetPackageId();
       }
 
-      // Add the mapping for package ID to index if not present.
       uint8_t idx = package_ids_[package_id];
       if (idx == 0xff) {
+        // Add the mapping for package ID to index if not present.
         package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
-        package_groups_.push_back({});
+        PackageGroup& new_group = package_groups_.emplace_back();
 
-        if (apk_assets->IsOverlay()) {
-          // The target package must precede the overlay package in the apk assets paths in order
-          // to take effect.
-          const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
-          auto target_package_iter = apk_assets_package_ids.find(
-              std::string(loaded_idmap->TargetApkPath()));
-          if (target_package_iter == apk_assets_package_ids.end()) {
-             LOG(INFO) << "failed to find target package for overlay "
-                       << loaded_idmap->OverlayApkPath();
-          } else {
-            const uint8_t target_package_id = target_package_iter->second;
-            const uint8_t target_idx = package_ids_[target_package_id];
-            CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
-                                      << " have an assigned package group";
-
-            PackageGroup& target_package_group = package_groups_[target_idx];
-
-            // Create a special dynamic reference table for the overlay to rewrite references to
-            // overlay resources as references to the target resources they overlay.
-            auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
-                loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
-            package_groups_.back().dynamic_ref_table = overlay_table;
-
-            // Add the overlay resource map to the target package's set of overlays.
-            target_package_group.overlays_.push_back(
-                ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
-                                                                      overlay_table.get()),
-                                  apk_assets_cookies[apk_assets]});
-          }
+        if (overlay_ref_table != nullptr) {
+          // If this package is from an overlay, use a dynamic reference table that can rewrite
+          // overlay resource ids to their corresponding target resource ids.
+          new_group.dynamic_ref_table = overlay_ref_table;
         }
 
-        DynamicRefTable* ref_table = package_groups_.back().dynamic_ref_table.get();
+        DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
         ref_table->mAssignedPackageId = package_id;
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
-      PackageGroup* package_group = &package_groups_[idx];
 
       // Add the package and to the set of packages with the same ID.
+      PackageGroup* package_group = &package_groups_[idx];
       package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
@@ -578,7 +582,7 @@
 
   const PackageGroup& package_group = package_groups_[package_idx];
   auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
-                                 stop_at_first_match, ignore_configuration);
+                                  stop_at_first_match, ignore_configuration);
   if (UNLIKELY(!result.has_value())) {
     return base::unexpected(result.error());
   }
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 23cacf8..f3c48f7 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -84,7 +84,7 @@
   return value_;
 }
 
-ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
+ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
                                      time_t last_mod_time)
     : zip_handle_(handle, ::CloseArchive),
       name_(std::forward<PathOrDebugName>(path)),
@@ -93,7 +93,7 @@
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path) {
   ZipArchiveHandle handle;
   if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
-    LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
+    LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
     CloseArchive(handle);
     return {};
   }
@@ -253,6 +253,14 @@
     return result == -1;
 }
 
+std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const {
+  ::ZipEntry entry;
+  if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
+    return {};
+  }
+  return entry.crc32;
+}
+
 const std::string& ZipAssetsProvider::GetDebugName() const {
   return name_.GetDebugName();
 }
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index f216f55..efd1f6a 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -54,12 +54,6 @@
 };
 
 struct Idmap_data_header {
-  uint8_t target_package_id;
-  uint8_t overlay_package_id;
-
-  // Padding to ensure 4 byte alignment for target_entry_count
-  uint16_t p0;
-
   uint32_t target_entry_count;
   uint32_t target_inline_entry_count;
   uint32_t overlay_entry_count;
@@ -158,19 +152,19 @@
     return {};
   }
 
-  // The resource ids encoded within the idmap are build-time resource ids.
-  target_res_id = (0x00FFFFFFU & target_res_id)
-      | (((uint32_t) data_header_->target_package_id) << 24U);
+  // The resource ids encoded within the idmap are build-time resource ids so do not consider the
+  // package id when determining if the resource in the target package is overlaid.
+  target_res_id &= 0x00FFFFFFU;
 
   // Check if the target resource is mapped to an overlay resource.
   auto first_entry = entries_;
   auto end_entry = entries_ + dtohl(data_header_->target_entry_count);
   auto entry = std::lower_bound(first_entry, end_entry, target_res_id,
-                                [](const Idmap_target_entry &e, const uint32_t target_id) {
-    return dtohl(e.target_id) < target_id;
+                                [](const Idmap_target_entry& e, const uint32_t target_id) {
+    return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
   });
 
-  if (entry != end_entry && dtohl(entry->target_id) == target_res_id) {
+  if (entry != end_entry && (0x00FFFFFFU & dtohl(entry->target_id)) == target_res_id) {
     uint32_t overlay_resource_id = dtohl(entry->overlay_id);
     // Lookup the resource without rewriting the overlay resource id back to the target resource id
     // being looked up.
@@ -182,12 +176,13 @@
   auto first_inline_entry = inline_entries_;
   auto end_inline_entry = inline_entries_ + dtohl(data_header_->target_inline_entry_count);
   auto inline_entry = std::lower_bound(first_inline_entry, end_inline_entry, target_res_id,
-                                       [](const Idmap_target_entry_inline &e,
+                                       [](const Idmap_target_entry_inline& e,
                                           const uint32_t target_id) {
-    return dtohl(e.target_id) < target_id;
+    return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
   });
 
-  if (inline_entry != end_inline_entry && dtohl(inline_entry->target_id) == target_res_id) {
+  if (inline_entry != end_inline_entry &&
+      (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
     return Result(inline_entry->value);
   }
   return {};
@@ -235,7 +230,7 @@
   }
   return std::string_view(data, *len);
 }
-}
+} // namespace
 
 LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
                          const Idmap_header* header,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2233827..30500ab 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include <algorithm>
+#include <fstream>
 #include <limits>
 #include <map>
 #include <memory>
@@ -44,6 +45,7 @@
 
 #ifdef __ANDROID__
 #include <binder/TextOutput.h>
+
 #endif
 
 #ifndef INT32_MAX
@@ -233,6 +235,15 @@
     fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
 }
 
+bool IsFabricatedOverlay(const std::string& path) {
+  std::ifstream fin(path);
+  uint32_t magic;
+  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
+    return magic == kFabricatedOverlayMagic;
+  }
+  return false;
+}
+
 static bool assertIdmapHeader(const void* idmap, size_t size) {
     if (reinterpret_cast<uintptr_t>(idmap) & 0x03) {
         ALOGE("idmap: header is not word aligned");
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 6fbd6aa..2255973f 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -101,7 +101,7 @@
   // Only pass invalidate_caches=false when it is known that the structure
   // change in ApkAssets is due to a safe addition of resources with completely
   // new resource IDs.
-  bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
+  bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
 
   inline const std::vector<const ApkAssets*> GetApkAssets() const {
     return apk_assets_;
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 7b06947..6f16ff4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -88,6 +88,8 @@
   WARN_UNUSED const std::string& GetDebugName() const override;
   WARN_UNUSED bool IsUpToDate() const override;
 
+  WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
+
   ~ZipAssetsProvider() override = default;
  protected:
   std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 0ded793..6804472 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -168,15 +168,14 @@
   }
 
   // Returns a mapping from target resource ids to overlay values.
-  const IdmapResMap GetTargetResourcesMap(
-      uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const {
+  const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
+                                          const OverlayDynamicRefTable* overlay_ref_table) const {
     return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
                        target_assigned_package_id, overlay_ref_table);
   }
 
   // Returns a dynamic reference table for a loaded overlay package.
-  const OverlayDynamicRefTable GetOverlayDynamicRefTable(
-      uint8_t target_assigned_package_id) const {
+  const OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
     return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
   }
 
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index bfd564c..168a863 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -43,8 +43,19 @@
 
 namespace android {
 
-constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const static uint32_t kIdmapCurrentVersion = 0x00000007u;
+constexpr const uint32_t kIdmapMagic = 0x504D4449u;
+constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u;
+
+// This must never change.
+constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
+
+// The version should only be changed when a backwards-incompatible change must be made to the
+// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
+// to prevent losing fabricated overlay data.
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+
+// Returns whether or not the path represents a fabricated overlay.
+bool IsFabricatedOverlay(const std::string& path);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 723413c..88eadcc 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 27be622..d5fee3f 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -35,7 +35,6 @@
 
 public:
     static DeviceInfo* get();
-    static float getMaxRefreshRate() { return get()->mMaxRefreshRate; }
     static int32_t getWidth() { return get()->mWidth; }
     static int32_t getHeight() { return get()->mHeight; }
     // Gets the density in density-independent pixels
@@ -45,7 +44,6 @@
     static int64_t getAppOffset() { return get()->mAppVsyncOffsetNanos; }
     // Sets the density in density-independent pixels
     static void setDensity(float density) { sDensity.store(density); }
-    static void setMaxRefreshRate(float refreshRate) { get()->mMaxRefreshRate = refreshRate; }
     static void setWidth(int32_t width) { get()->mWidth = width; }
     static void setHeight(int32_t height) { get()->mHeight = height; }
     static void setRefreshRate(float refreshRate) {
@@ -91,7 +89,6 @@
     SkColorType mWideColorType = SkColorType::kN32_SkColorType;
     int mDisplaysSize = 0;
     int mPhysicalDisplayIndex = -1;
-    float mMaxRefreshRate = 60.0;
     int32_t mWidth = 1080;
     int32_t mHeight = 1920;
     int64_t mVsyncPeriod = 16666666;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e798f2a..971a53a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -79,7 +79,6 @@
 bool Properties::isolatedProcess = false;
 
 int Properties::contextPriority = 0;
-int Properties::defaultRenderAhead = -1;
 float Properties::defaultSdrWhitePoint = 200.f;
 
 bool Properties::load() {
@@ -129,10 +128,6 @@
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
 
-    defaultRenderAhead = std::max(
-            -1,
-            std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD, render_ahead().value_or(-1))));
-
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1639143..dcb79ba 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -162,8 +162,6 @@
  */
 #define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
 
-#define PROPERTY_RENDERAHEAD "debug.hwui.render_ahead"
-
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -247,8 +245,6 @@
 
     static int contextPriority;
 
-    static int defaultRenderAhead;
-
     static float defaultSdrWhitePoint;
 
 private:
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 8f455fe..1842356 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -355,29 +355,41 @@
     env->SetStaticObjectField(cls, fid, typeface);
 }
 
+// Critical Native
+static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
+    return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
+    std::shared_ptr<minikin::FontFamily> family =
+            toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
+    return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
 
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gTypefaceMethods[] = {
-    { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
-    { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
-            (void*)Typeface_createFromTypefaceWithExactStyle },
-    { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
-            (void*)Typeface_createFromTypefaceWithVariation },
-    { "nativeCreateWeightAlias",  "(JI)J", (void*)Typeface_createWeightAlias },
-    { "nativeGetReleaseFunc",     "()J",  (void*)Typeface_getReleaseFunc },
-    { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
-    { "nativeGetWeight",      "(J)I",  (void*)Typeface_getWeight },
-    { "nativeCreateFromArray",    "([JJII)J",
-                                           (void*)Typeface_createFromArray },
-    { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
-    { "nativeGetSupportedAxes",   "(J)[I",  (void*)Typeface_getSupportedAxes },
-    { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
-          (void*)Typeface_registerGenericFamily },
-    { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
-    { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
-    { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
-          (void*)Typeface_forceSetStaticFinalField },
+        {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface},
+        {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
+         (void*)Typeface_createFromTypefaceWithExactStyle},
+        {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
+         (void*)Typeface_createFromTypefaceWithVariation},
+        {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias},
+        {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc},
+        {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle},
+        {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight},
+        {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray},
+        {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault},
+        {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
+        {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
+         (void*)Typeface_registerGenericFamily},
+        {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
+        {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+        {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
+         (void*)Typeface_forceSetStaticFinalField},
+        {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
+        {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index a146b64..4966bfa 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -603,14 +603,12 @@
 
 static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth,
                                                           jint physicalHeight, jfloat refreshRate,
-                                                          jfloat maxRefreshRate,
                                                           jint wideColorDataspace,
                                                           jlong appVsyncOffsetNanos,
                                                           jlong presentationDeadlineNanos) {
     DeviceInfo::setWidth(physicalWidth);
     DeviceInfo::setHeight(physicalHeight);
     DeviceInfo::setRefreshRate(refreshRate);
-    DeviceInfo::setMaxRefreshRate(maxRefreshRate);
     DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace));
     DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
     DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
@@ -735,7 +733,7 @@
         {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
         {"nSetDisplayDensityDpi", "(I)V",
          (void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
-        {"nInitDisplayInfo", "(IIFFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+        {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
         {"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
 };
 
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 3392dac..c8471a9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -219,7 +219,7 @@
 }
 
 // Critical Native
-static jlong Font_getReleaseNativeFontFunc() {
+static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) {
     return reinterpret_cast<jlong>(releaseFont);
 }
 
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 8096479..b682135 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -101,13 +101,13 @@
 }
 
 // CriticalNative
-static jint FontFamily_getFontSize(jlong familyPtr) {
+static jint FontFamily_getFontSize(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr) {
     FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr);
     return family->family->getNumFonts();
 }
 
 // CriticalNative
-static jlong FontFamily_getFont(jlong familyPtr, jint index) {
+static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint index) {
     FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr);
     std::shared_ptr<minikin::Font> font = family->family->getFontRef(index);
     return reinterpret_cast<jlong>(new FontWrapper(std::move(font)));
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 37a6ee7..65afcc3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -108,7 +108,6 @@
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mProfiler.setDensity(DeviceInfo::getDensity());
-    setRenderAheadDepth(Properties::defaultRenderAhead);
 }
 
 CanvasContext::~CanvasContext() {
@@ -157,24 +156,17 @@
 void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
     ATRACE_CALL();
 
-    if (mFixedRenderAhead) {
-        mRenderAheadCapacity = mRenderAheadDepth;
-    } else {
-        if (DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
-            mRenderAheadCapacity = 1;
-        } else {
-            mRenderAheadCapacity = 0;
-        }
-    }
-
     if (window) {
+        int extraBuffers = 0;
+        native_window_get_extra_buffer_count(window, &extraBuffers);
+
         mNativeSurface = std::make_unique<ReliableSurface>(window);
         mNativeSurface->init();
         if (enableTimeout) {
             // TODO: Fix error handling & re-shorten timeout
             ANativeWindow_setDequeueTimeout(window, 4000_ms);
         }
-        mNativeSurface->setExtraBufferCount(mRenderAheadCapacity);
+        mNativeSurface->setExtraBufferCount(extraBuffers);
     } else {
         mNativeSurface = nullptr;
     }
@@ -441,24 +433,6 @@
     mRenderThread.pushBackFrameCallback(this);
 }
 
-void CanvasContext::setPresentTime() {
-    int64_t presentTime = NATIVE_WINDOW_TIMESTAMP_AUTO;
-    int renderAhead = 0;
-    const auto frameIntervalNanos = mRenderThread.timeLord().frameIntervalNanos();
-    if (mFixedRenderAhead) {
-        renderAhead = std::min(mRenderAheadDepth, mRenderAheadCapacity);
-    } else if (frameIntervalNanos < 15_ms) {
-        renderAhead = std::min(1, static_cast<int>(mRenderAheadCapacity));
-    }
-
-    if (renderAhead) {
-        presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) +
-                (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() +
-                (frameIntervalNanos / 2);
-    }
-    native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime);
-}
-
 void CanvasContext::draw() {
     SkRect dirty;
     mDamageAccumulator.finish(&dirty);
@@ -478,8 +452,6 @@
     mCurrentFrameInfo->markIssueDrawCommandsStart();
 
     Frame frame = mRenderPipeline->getFrame();
-    setPresentTime();
-
     SkRect windowDirty = computeDirtyRect(frame, &dirty);
 
     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
@@ -765,19 +737,6 @@
     return width != mLastFrameWidth || height != mLastFrameHeight;
 }
 
-void CanvasContext::setRenderAheadDepth(int renderAhead) {
-    if (renderAhead > 2 || renderAhead < -1 || mNativeSurface) {
-        return;
-    }
-    if (renderAhead == -1) {
-        mFixedRenderAhead = false;
-        mRenderAheadDepth = 0;
-    } else {
-        mFixedRenderAhead = true;
-        mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
-    }
-}
-
 SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
     if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
         // can't rely on prior content of window if viewport size changes
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index cc4eb32..b31883b 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -193,9 +193,6 @@
         return mUseForceDark;
     }
 
-    // Must be called before setSurface
-    void setRenderAheadDepth(int renderAhead);
-
     SkISize getNextFrameSize() const;
 
 private:
@@ -211,7 +208,6 @@
 
     bool isSwapChainStuffed();
     bool surfaceRequiresRedraw();
-    void setPresentTime();
     void setupPipelineSurface();
 
     SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
@@ -232,9 +228,6 @@
     // painted onto its surface.
     bool mIsDirty = false;
     SwapBehavior mSwapBehavior = SwapBehavior::kSwap_default;
-    bool mFixedRenderAhead = false;
-    uint32_t mRenderAheadDepth = 0;
-    uint32_t mRenderAheadCapacity = 0;
     struct SwapHistory {
         SkRect damage;
         nsecs_t vsyncTime;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b51f6dc..0ade8dd 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -295,11 +295,6 @@
     mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
 }
 
-void RenderProxy::setRenderAheadDepth(int renderAhead) {
-    mRenderThread.queue().post(
-            [context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); });
-}
-
 int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
                                  SkBitmap* bitmap) {
     auto& thread = RenderThread::getInstance();
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 33dabc9..a4adb16 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -123,23 +123,6 @@
     void removeFrameMetricsObserver(FrameMetricsObserver* observer);
     void setForceDark(bool enable);
 
-    /**
-     * Sets a render-ahead depth on the backing renderer. This will increase latency by
-     * <swapInterval> * renderAhead and increase memory usage by (3 + renderAhead) * <resolution>.
-     * In return the renderer will be less susceptible to jitter, resulting in a smoother animation.
-     *
-     * Not recommended to use in response to anything touch driven, but for canned animations
-     * where latency is not a concern careful use may be beneficial.
-     *
-     * Note that when increasing this there will be a frame gap of N frames where N is
-     * renderAhead - <current renderAhead>. When decreasing this if there are any pending
-     * frames they will retain their prior renderAhead value, so it will take a few frames
-     * for the decrease to flush through.
-     *
-     * @param renderAhead How far to render ahead, must be in the range [0..2]
-     */
-    void setRenderAheadDepth(int renderAhead);
-
     static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
                                            int bottom, SkBitmap* bitmap);
     static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 74a039b..91022cf 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -38,7 +38,6 @@
         int count = 0;
         int reportFrametimeWeight = 0;
         bool renderOffscreen = true;
-        int renderAhead = 0;
     };
 
     template <class T>
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index eda5d22..8c7d261 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -153,11 +153,6 @@
     proxy->resetProfileInfo();
     proxy->fence();
 
-    if (opts.renderAhead) {
-        usleep(33000);
-    }
-    proxy->setRenderAheadDepth(opts.renderAhead);
-
     ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);
 
     nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC);
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 88d33c3..174a140 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -69,7 +69,6 @@
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
   --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
-  --render-ahead=NUM   Sets how far to render-ahead. Must be 0 (default), 1, or 2.
 )");
 }
 
@@ -171,7 +170,6 @@
     Onscreen,
     Offscreen,
     Renderer,
-    RenderAhead,
 };
 }
 
@@ -187,7 +185,6 @@
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
         {"renderer", required_argument, nullptr, LongOpts::Renderer},
-        {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead},
         {0, 0, 0, 0}};
 
 static const char* SHORT_OPTIONS = "c:r:h";
@@ -286,16 +283,6 @@
                 gOpts.renderOffscreen = true;
                 break;
 
-            case LongOpts::RenderAhead:
-                if (!optarg) {
-                    error = true;
-                }
-                gOpts.renderAhead = atoi(optarg);
-                if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) {
-                    error = true;
-                }
-                break;
-
             case 'h':
                 printHelp();
                 exit(EXIT_SUCCESS);
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index d291ec0..438a92e 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -95,7 +95,7 @@
     name: "libincident_test",
     test_config: "AndroidTest.xml",
     defaults: ["libincidentpriv_defaults"],
-    test_suites: ["device-tests", "mts"],
+    test_suites: ["device-tests", "mts-statsd"],
     compile_multilib: "both",
     multilib: {
         lib64: {
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index d40de76..205c1f4 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -354,7 +354,7 @@
 
     /**
      * @hide
-     * @return the internal device tyoe
+     * @return the internal device type
      */
     public int getInternalType() {
         return mPort.type();
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 02fa94c..5a7ff7f 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -33,7 +33,8 @@
         float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
     oneway void stop(IBinder token);
     boolean isPlaying(IBinder token);
-    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping);
+    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
+        boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
     oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index bd783ce..79d505e 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -24,6 +24,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
+import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -77,6 +78,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private MediaPlayer mLocalPlayer;
     private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+    private HapticGenerator mHapticGenerator;
 
     @UnsupportedAppUsage
     private Uri mUri;
@@ -89,6 +91,7 @@
     // playback properties, use synchronized with mPlaybackSettingsLock
     private boolean mIsLooping = false;
     private float mVolume = 1.0f;
+    private boolean mHapticGeneratorEnabled = false;
     private final Object mPlaybackSettingsLock = new Object();
 
     /** {@hide} */
@@ -197,15 +200,50 @@
     }
 
     /**
+     * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
+     * only be enabled on devices that support the effect.
+     *
+     * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
+     * @see android.media.audiofx.HapticGenerator#isAvailable()
+     */
+    public boolean setHapticGeneratorEnabled(boolean enabled) {
+        if (!HapticGenerator.isAvailable()) {
+            return false;
+        }
+        synchronized (mPlaybackSettingsLock) {
+            mHapticGeneratorEnabled = enabled;
+            applyPlaybackProperties_sync();
+        }
+        return true;
+    }
+
+    /**
+     * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
+     * @return true if the HapticGenerator is enabled.
+     */
+    public boolean isHapticGeneratorEnabled() {
+        synchronized (mPlaybackSettingsLock) {
+            return mHapticGeneratorEnabled;
+        }
+    }
+
+    /**
      * Must be called synchronized on mPlaybackSettingsLock
      */
     private void applyPlaybackProperties_sync() {
         if (mLocalPlayer != null) {
             mLocalPlayer.setVolume(mVolume);
             mLocalPlayer.setLooping(mIsLooping);
+            if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+            }
+            if (mHapticGenerator != null) {
+                mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+            }
         } else if (mAllowRemote && (mRemotePlayer != null)) {
             try {
-                mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
+                mRemotePlayer.setPlaybackProperties(
+                        mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
             } catch (RemoteException e) {
                 Log.w(TAG, "Problem setting playback properties: ", e);
             }
@@ -413,6 +451,10 @@
 
     private void destroyLocalPlayer() {
         if (mLocalPlayer != null) {
+            if (mHapticGenerator != null) {
+                mHapticGenerator.release();
+                mHapticGenerator = null;
+            }
             mLocalPlayer.setOnCompletionListener(null);
             mLocalPlayer.reset();
             mLocalPlayer.release();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98b9ad8..740bc2d 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2526,6 +2526,7 @@
          * Pauses TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#pauseRecording(Bundle)}.
          */
         void pauseRecording(@NonNull Bundle params) {
             if (mToken == null) {
@@ -2543,6 +2544,7 @@
          * Resumes TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#resumeRecording(Bundle)}.
          */
         void resumeRecording(@NonNull Bundle params) {
             if (mToken == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b64d7..4972529 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -180,6 +180,10 @@
         "libstagefright_foundation_headers",
     ],
 
+    // TunerService is a system service required for Tuner feature.
+    // TunerJNI is a client of TunerService so we build the dependency here.
+    required: ["mediatuner"],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h
index 185b2f6..508dccf 100644
--- a/media/jni/tuner/ClientHelper.h
+++ b/media/jni/tuner/ClientHelper.h
@@ -19,6 +19,7 @@
 
 #include <android/binder_parcel_utils.h>
 #include <android/hardware/tv/tuner/1.1/types.h>
+#include <utils/Log.h>
 
 using Status = ::ndk::ScopedAStatus;
 
@@ -37,6 +38,7 @@
         } else if (s.isOk()) {
             return Result::SUCCESS;
         }
+        ALOGE("Aidl exception code %s", s.getDescription().c_str());
         return Result::UNKNOWN_ERROR;
     }
 };
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8b4ca37..f618890 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
 using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
 using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
 
 namespace android {
@@ -480,7 +481,7 @@
         case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
             int size = ipAddr.srcIpAddress.v4().size();
             srcIpAddress.isIpV6 = false;
-            srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+            srcIpAddress.addr.resize(size);
             copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
                     srcIpAddress.addr.begin());
             break;
@@ -493,8 +494,6 @@
                     srcIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
     switch (ipAddr.dstIpAddress.getDiscriminator()) {
         case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
@@ -513,8 +512,6 @@
                     dstIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -696,8 +693,6 @@
             getHidlRestartEvent(filterEvents, eventExt);
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -883,19 +878,18 @@
         DemuxFilterEventExt& eventExt) {
     auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
     eventExt.events.resize(1);
+    DemuxFilterMonitorEvent monitorEvent;
     switch (monitor.getTag()) {
         case TunerFilterMonitorEvent::scramblingStatus: {
-            eventExt.events[0].monitorEvent().scramblingStatus(
-                    static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
         case TunerFilterMonitorEvent::cid: {
-            eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+            monitorEvent.cid(static_cast<uint32_t>(monitor.cid));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
-        default:
-            eventExt.events[0].noinit();
-            break;
     }
 }
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 3a00133..0613223 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -49,9 +49,12 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
 using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
 using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
@@ -61,19 +64,22 @@
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval;
+using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendRollOff;
 using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendType;
 
 namespace android {
 
 /////////////// FrontendClient ///////////////////////
 
-FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) {
+FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
     mTunerFrontend = tunerFrontend;
     mAidlCallback = NULL;
     mHidlCallback = NULL;
-    mId = id;
     mType = type;
 }
 
@@ -104,6 +110,11 @@
     mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend);
 }
 
+// TODO: move after migration is done
+void FrontendClient::setId(int id) {
+    mId = id;
+}
+
 Result FrontendClient::tune(const FrontendSettings& settings,
         const FrontendSettingsExt1_1& settingsExt1_1) {
     if (mTunerFrontend != NULL) {
@@ -333,13 +344,26 @@
 }
 
 int FrontendClient::getId() {
-    return mId;
+    if (mTunerFrontend != NULL) {
+        Status s = mTunerFrontend->getFrontendId(&mId);
+        if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+            return mId;
+        }
+        ALOGE("Failed to getFrontendId from Tuner Frontend");
+        return -1;
+    }
+
+    if (mFrontend != NULL) {
+        return mId;
+    }
+
+    return -1;
 }
 
 vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
     vector<FrontendStatus> hidlStatus;
     for (TunerFrontendStatus s : aidlStatus) {
-        FrontendStatus status;
+        FrontendStatus status = FrontendStatus();
         switch (s.getTag()) {
             case TunerFrontendStatus::isDemodLocked: {
                 status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
@@ -389,25 +413,31 @@
             }
             case TunerFrontendStatus::modulation: {
                 auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+                FrontendModulationStatus modulation;
                 switch (mType) {
                     case (int)FrontendType::DVBC:
-                        status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBS:
-                        status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -466,7 +496,7 @@
             }
             case TunerFrontendStatus::hierarchy: {
                 status.hierarchy(static_cast<FrontendDvbtHierarchy>(
-                        s.get<TunerFrontendStatus::freqOffset>()));
+                        s.get<TunerFrontendStatus::hierarchy>()));
                 hidlStatus.push_back(status);
                 break;
             }
@@ -477,15 +507,16 @@
             }
             case TunerFrontendStatus::plpInfo: {
                 int size = s.get<TunerFrontendStatus::plpInfo>().size();
-                status.plpInfo().resize(size);
+                hidl_vec<FrontendStatusAtsc3PlpInfo> info(size);
                 for (int i = 0; i < size; i++) {
                     auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
-                    status.plpInfo()[i] = {
+                    info[i] = {
                         .plpId = (uint8_t)aidlInfo.plpId,
                         .isLocked = aidlInfo.isLocked,
                         .uec = (uint32_t)aidlInfo.uec,
                     };
                 }
+                status.plpInfo(info);
                 hidlStatus.push_back(status);
                 break;
             }
@@ -503,52 +534,54 @@
         FrontendStatusExt1_1 status;
         switch (s.getTag()) {
             case TunerFrontendStatus::modulations: {
+                vector<FrontendModulation> ms;
                 for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
-                    int size = status.modulations().size();
-                    status.modulations().resize(size + 1);
+                    FrontendModulation m;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.modulations()[size].dvbc(
-                                    static_cast<FrontendDvbcModulation>(aidlMod));
+                            m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBS:
-                            status.modulations()[size].dvbs(
-                                    static_cast<FrontendDvbsModulation>(aidlMod));
+                            m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBT:
-                            status.modulations()[size].dvbt(
-                                    static_cast<FrontendDvbtConstellation>(aidlMod));
+                            m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS:
-                            status.modulations()[size].isdbs(
-                                    static_cast<FrontendIsdbsModulation>(aidlMod));
+                            m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS3:
-                            status.modulations()[size].isdbs3(
-                                    static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBT:
-                            status.modulations()[size].isdbt(
-                                    static_cast<FrontendIsdbtModulation>(aidlMod));
+                            m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC:
-                            status.modulations()[size].atsc(
-                                    static_cast<FrontendAtscModulation>(aidlMod));
+                            m.atsc(static_cast<FrontendAtscModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.modulations()[size].atsc3(
-                                    static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.modulations()[size].dtmb(
-                                    static_cast<FrontendDtmbModulation>(aidlMod));
+                            m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         default:
-                            status.modulations().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (ms.size() > 0) {
+                    status.modulations(ms);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::bers: {
@@ -571,25 +604,31 @@
             }
             case TunerFrontendStatus::bandwidth: {
                 auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+                FrontendBandwidth band;
                 switch (mType) {
                     case (int)FrontendType::ATSC3:
-                        status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBC:
-                        status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBT:
-                        status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -599,17 +638,21 @@
             }
             case TunerFrontendStatus::interval: {
                 auto aidlInter = s.get<TunerFrontendStatus::interval>();
+                FrontendGuardInterval inter;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -619,19 +662,21 @@
             }
             case TunerFrontendStatus::transmissionMode: {
                 auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+                FrontendTransmissionMode trans;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.transmissionMode().dvbt(
-                                static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.transmissionMode().dtmb(
-                                static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -650,28 +695,30 @@
                 break;
             }
             case TunerFrontendStatus::interleaving: {
+                vector<FrontendInterleaveMode> modes;
                 for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
-                    int size = status.interleaving().size();
-                    status.interleaving().resize(size + 1);
+                    FrontendInterleaveMode mode;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.interleaving()[size].dvbc(
-                                    static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.interleaving()[size].atsc3(
-                                    static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.interleaving()[size].dtmb(
-                                    static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         default:
-                            status.interleaving().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (modes.size() > 0) {
+                    status.interleaving(modes);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::isdbtSegment: {
@@ -690,17 +737,21 @@
             }
             case TunerFrontendStatus::rollOff: {
                 auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+                FrontendRollOff roll;
                 switch (mType) {
                     case (int)FrontendType::DVBS:
-                        status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     default:
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 298b397..f71616c 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -108,7 +108,7 @@
 struct FrontendClient : public RefBase {
 
 public:
-    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type);
+    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type);
     ~FrontendClient();
 
     /**
@@ -180,6 +180,7 @@
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
 
+    void setId(int id);
     int getId();
 
 private:
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 7f954b5..cf17ed6 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -46,13 +46,12 @@
     // Connect with Tuner Service.
     ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
     mTunerService = ITunerService::fromBinder(binder);
-    // TODO: Remove after JNI migration is done.
-    mTunerService = NULL;
     if (mTunerService == NULL) {
         ALOGE("Failed to get tuner service");
     } else {
         // TODO: b/178124017 update TRM in TunerService independently.
         mTunerService->updateTunerResources();
+        mTunerService->getTunerHalVersion(&mTunerVersion);
     }
 }
 
@@ -115,7 +114,7 @@
         if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
             return NULL;
         }
-        return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type);
+        return new FrontendClient(tunerFrontend, aidlFrontendInfo.type);
     }
 
     if (mTuner != NULL) {
@@ -127,8 +126,10 @@
             if (res != Result::SUCCESS) {
                 return NULL;
             }
-            sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type);
+            sp<FrontendClient> frontendClient = new FrontendClient(
+                    NULL, (int)hidlInfo.type);
             frontendClient->setHidlFrontend(hidlFrontend);
+            frontendClient->setId(id);
             return frontendClient;
         }
     }
@@ -358,7 +359,7 @@
 
 sp<ITuner> TunerClient::getHidlTuner() {
     if (mTuner == NULL) {
-        mTunerVersion = 0;
+        mTunerVersion = TUNER_HAL_VERSION_UNKNOWN;
         mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
 
         if (mTuner_1_1 == NULL) {
@@ -367,11 +368,11 @@
             if (mTuner == NULL) {
                 ALOGW("Failed to get tuner 1.0 service.");
             } else {
-                mTunerVersion = 1 << 16;
+                mTunerVersion = TUNER_HAL_VERSION_1_0;
             }
         } else {
             mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
-            mTunerVersion = ((1 << 16) | 1);
+            mTunerVersion = TUNER_HAL_VERSION_1_1;
          }
      }
      return mTuner;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 744bf20..9671cf7 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -48,6 +48,10 @@
 
 namespace android {
 
+const static int TUNER_HAL_VERSION_UNKNOWN = 0;
+const static int TUNER_HAL_VERSION_1_0 = 1 << 16;
+const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1;
+
 typedef enum {
     FRONTEND,
     LNB,
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index fd71670..606cd57 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -322,7 +322,8 @@
 
     void onDeviceSelected(String callingPackage, String deviceAddress) {
         mServiceCallback.complete(new Association(
-                getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false));
+                getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false,
+                System.currentTimeMillis()));
     }
 
     void onCancel() {
diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
index 9b56b23..f4b46e9 100644
--- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
+++ b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
@@ -16,12 +16,15 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -40,10 +43,29 @@
     private final long mExpiryTimeMillis;
     private final boolean mCaptive;
     private final String mVenueFriendlyName;
+    private final int mVenueInfoUrlSource;
+    private final int mTermsAndConditionsSource;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CAPTIVE_PORTAL_DATA_SOURCE_"}, value = {
+            CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+            CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT})
+    public @interface CaptivePortalDataSource {}
+
+    /**
+     * Source of information: Other (default)
+     */
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0;
+
+    /**
+     * Source of information: Wi-Fi Passpoint
+     */
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1;
 
     private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
             boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive,
-            String venueFriendlyName) {
+            String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) {
         mRefreshTimeMillis = refreshTimeMillis;
         mUserPortalUrl = userPortalUrl;
         mVenueInfoUrl = venueInfoUrl;
@@ -52,11 +74,14 @@
         mExpiryTimeMillis = expiryTimeMillis;
         mCaptive = captive;
         mVenueFriendlyName = venueFriendlyName;
+        mVenueInfoUrlSource = venueInfoUrlSource;
+        mTermsAndConditionsSource = termsAndConditionsSource;
     }
 
     private CaptivePortalData(Parcel p) {
         this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
-                p.readLong(), p.readLong(), p.readBoolean(), p.readString());
+                p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(),
+                p.readInt());
     }
 
     @Override
@@ -74,6 +99,8 @@
         dest.writeLong(mExpiryTimeMillis);
         dest.writeBoolean(mCaptive);
         dest.writeString(mVenueFriendlyName);
+        dest.writeInt(mVenueInfoUrlSource);
+        dest.writeInt(mTermsAndConditionsSource);
     }
 
     /**
@@ -88,6 +115,9 @@
         private long mExpiryTime = -1;
         private boolean mCaptive;
         private String mVenueFriendlyName;
+        private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
+        private @CaptivePortalDataSource int mUserPortalUrlSource =
+                CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
 
         /**
          * Create an empty builder.
@@ -100,8 +130,8 @@
         public Builder(@Nullable CaptivePortalData data) {
             if (data == null) return;
             setRefreshTime(data.mRefreshTimeMillis)
-                    .setUserPortalUrl(data.mUserPortalUrl)
-                    .setVenueInfoUrl(data.mVenueInfoUrl)
+                    .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource)
+                    .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource)
                     .setSessionExtendable(data.mIsSessionExtendable)
                     .setBytesRemaining(data.mByteLimit)
                     .setExpiryTime(data.mExpiryTimeMillis)
@@ -123,7 +153,18 @@
          */
         @NonNull
         public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
+            return setUserPortalUrl(userPortalUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+        }
+
+        /**
+         * Set the URL to be used for users to login to the portal, if captive, and the source of
+         * the data, see {@link CaptivePortalDataSource}
+         */
+        @NonNull
+        public Builder setUserPortalUrl(@Nullable Uri userPortalUrl,
+                @CaptivePortalDataSource int source) {
             mUserPortalUrl = userPortalUrl;
+            mUserPortalUrlSource = source;
             return this;
         }
 
@@ -132,7 +173,18 @@
          */
         @NonNull
         public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
+            return setVenueInfoUrl(venueInfoUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+        }
+
+        /**
+         * Set the URL that can be used by users to view information about the network venue, and
+         * the source of the data, see {@link CaptivePortalDataSource}
+         */
+        @NonNull
+        public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl,
+                @CaptivePortalDataSource int source) {
             mVenueInfoUrl = venueInfoUrl;
+            mVenueInfoUrlSource = source;
             return this;
         }
 
@@ -188,7 +240,8 @@
         public CaptivePortalData build() {
             return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
                     mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive,
-                    mVenueFriendlyName);
+                    mVenueFriendlyName, mVenueInfoUrlSource,
+                    mUserPortalUrlSource);
         }
     }
 
@@ -249,6 +302,22 @@
     }
 
     /**
+     * Get the information source of the Venue URL
+     * @return The source that the Venue URL was obtained from
+     */
+    public @CaptivePortalDataSource int getVenueInfoUrlSource() {
+        return mVenueInfoUrlSource;
+    }
+
+    /**
+     * Get the information source of the user portal URL
+     * @return The source that the user portal URL was obtained from
+     */
+    public @CaptivePortalDataSource int getUserPortalUrlSource() {
+        return mTermsAndConditionsSource;
+    }
+
+    /**
      * Get the venue friendly name
      */
     @Nullable
@@ -272,7 +341,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
-                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName);
+                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName,
+                mVenueInfoUrlSource, mTermsAndConditionsSource);
     }
 
     @Override
@@ -286,7 +356,9 @@
                 && mByteLimit == other.mByteLimit
                 && mExpiryTimeMillis == other.mExpiryTimeMillis
                 && mCaptive == other.mCaptive
-                && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName);
+                && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName)
+                && mVenueInfoUrlSource == other.mVenueInfoUrlSource
+                && mTermsAndConditionsSource == other.mTermsAndConditionsSource;
     }
 
     @Override
@@ -300,6 +372,8 @@
                 + ", expiryTime: " + mExpiryTimeMillis
                 + ", captive: " + mCaptive
                 + ", venueFriendlyName: " + mVenueFriendlyName
+                + ", venueInfoUrlSource: " + mVenueInfoUrlSource
+                + ", termsAndConditionsSource: " + mTermsAndConditionsSource
                 + "}";
     }
 }
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index a746139..fbe15bb 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkRequest.Type.LISTEN;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 import static android.net.QosCallback.QosCallbackRegistrationException;
 
 import android.annotation.CallbackExecutor;
@@ -3721,7 +3722,8 @@
         printStackTrace();
         checkCallbackNotNull(callback);
         Preconditions.checkArgument(
-                reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities");
+                reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null,
+                "null NetworkCapabilities");
         final NetworkRequest request;
         final String callingPackageName = mContext.getOpPackageName();
         try {
@@ -4192,8 +4194,9 @@
     }
 
     /**
-     * Registers to receive notifications about changes in the system default network. The callbacks
-     * will continue to be called until either the application exits or
+     * Registers to receive notifications about changes in the application's default network. This
+     * may be a physical network or a virtual network, such as a VPN that applies to the
+     * application. The callbacks will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
      *
      * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
@@ -4206,7 +4209,7 @@
      * {@link #unregisterNetworkCallback(NetworkCallback)}.
      *
      * @param networkCallback The {@link NetworkCallback} that the system will call as the
-     *                        system default network changes.
+     *                        application's default network changes.
      *                        The callback is invoked on the default internal Handler.
      * @throws RuntimeException if the app already has too many callbacks registered.
      */
@@ -4216,10 +4219,46 @@
     }
 
     /**
+     * Registers to receive notifications about changes in the application's default network. This
+     * may be a physical network or a virtual network, such as a VPN that applies to the
+     * application. The callbacks will continue to be called until either the application exits or
+     * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+     *
+     * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+     * number of outstanding requests to 100 per app (identified by their UID), shared with
+     * all variants of this method, of {@link #requestNetwork} as well as
+     * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+     * Requesting a network with this method will count toward this limit. If this limit is
+     * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+     * make sure to unregister the callbacks with
+     * {@link #unregisterNetworkCallback(NetworkCallback)}.
+     *
+     * @param networkCallback The {@link NetworkCallback} that the system will call as the
+     *                        application's default network changes.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @throws RuntimeException if the app already has too many callbacks registered.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+            @NonNull Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+                TRACK_DEFAULT, TYPE_NONE, cbHandler);
+    }
+
+    /**
      * Registers to receive notifications about changes in the system default network. The callbacks
      * will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
      *
+     * This method should not be used to determine networking state seen by applications, because in
+     * many cases, most or even all application traffic may not use the default network directly,
+     * and traffic from different applications may go on different networks by default. As an
+     * example, if a VPN is connected, traffic from all applications might be sent through the VPN
+     * and not onto the system default network. Applications or system components desiring to do
+     * determine network state as seen by applications should use other methods such as
+     * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}.
+     *
      * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
      * number of outstanding requests to 100 per app (identified by their UID), shared with
      * all variants of this method, of {@link #requestNetwork} as well as
@@ -4233,20 +4272,19 @@
      *                        system default network changes.
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
      * @throws RuntimeException if the app already has too many callbacks registered.
+     *
+     * @hide
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+    @SystemApi(client = MODULE_LIBRARIES)
+    @SuppressLint({"ExecutorRegistration", "PairedRegistration"})
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
             @NonNull Handler handler) {
-        // This works because if the NetworkCapabilities are null,
-        // ConnectivityService takes them from the default request.
-        //
-        // Since the capabilities are exactly the same as the default request's
-        // capabilities, this request is guaranteed, at all times, to be
-        // satisfied by the same network, if any, that satisfies the default
-        // request, i.e., the system default network.
         CallbackHandler cbHandler = new CallbackHandler(handler);
         sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
-                TRACK_DEFAULT, TYPE_NONE, cbHandler);
+                TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler);
     }
 
     /**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 55b2c3c..9d67f0b 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -762,12 +762,14 @@
         final int originalSignalStrength = mSignalStrength;
         final int originalOwnerUid = getOwnerUid();
         final int[] originalAdministratorUids = getAdministratorUids();
+        final TransportInfo originalTransportInfo = getTransportInfo();
         clearAll();
         mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS)
                 | (1 << TRANSPORT_TEST);
         mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
+        mTransportInfo = originalTransportInfo;
 
         // Only retain the owner and administrator UIDs if they match the app registering the remote
         // caller that registered the network.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 6540397..b4a651c 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -104,17 +104,14 @@
      *       callbacks about the single, highest scoring current network
      *       (if any) that matches the specified NetworkCapabilities, or
      *
-     *     - TRACK_DEFAULT, a hybrid of the two designed such that the
-     *       framework will issue callbacks for the single, highest scoring
-     *       current network (if any) that matches the capabilities of the
-     *       default Internet request (mDefaultRequest), but which cannot cause
-     *       the framework to either create or retain the existence of any
-     *       specific network. Note that from the point of view of the request
-     *       matching code, TRACK_DEFAULT is identical to REQUEST: its special
-     *       behaviour is not due to different semantics, but to the fact that
-     *       the system will only ever create a TRACK_DEFAULT with capabilities
-     *       that are identical to the default request's capabilities, thus
-     *       causing it to share fate in every way with the default request.
+     *     - TRACK_DEFAULT, which causes the framework to issue callbacks for
+     *       the single, highest scoring current network (if any) that will
+     *       be chosen for an app, but which cannot cause the framework to
+     *       either create or retain the existence of any specific network.
+     *
+     *     - TRACK_SYSTEM_DEFAULT, which causes the framework to send callbacks
+     *       for the network (if any) that satisfies the default Internet
+     *       request.
      *
      *     - BACKGROUND_REQUEST, like REQUEST but does not cause any networks
      *       to retain the NET_CAPABILITY_FOREGROUND capability. A network with
@@ -137,6 +134,7 @@
         TRACK_DEFAULT,
         REQUEST,
         BACKGROUND_REQUEST,
+        TRACK_SYSTEM_DEFAULT,
     };
 
     /**
@@ -601,6 +599,8 @@
                 return NetworkRequestProto.TYPE_REQUEST;
             case BACKGROUND_REQUEST:
                 return NetworkRequestProto.TYPE_BACKGROUND_REQUEST;
+            case TRACK_SYSTEM_DEFAULT:
+                return NetworkRequestProto.TYPE_TRACK_SYSTEM_DEFAULT;
             default:
                 return NetworkRequestProto.TYPE_UNKNOWN;
         }
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/packages/Connectivity/framework/src/android/net/VpnManager.java
index 1812509..1e30283 100644
--- a/packages/Connectivity/framework/src/android/net/VpnManager.java
+++ b/packages/Connectivity/framework/src/android/net/VpnManager.java
@@ -55,13 +55,29 @@
 public class VpnManager {
     /** Type representing a lack of VPN @hide */
     public static final int TYPE_VPN_NONE = -1;
-    /** VPN service type code @hide */
+
+    /**
+     * A VPN created by an app using the {@link VpnService} API.
+     * @hide
+     */
     public static final int TYPE_VPN_SERVICE = 1;
-    /** Platform VPN type code @hide */
+
+    /**
+     * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}.
+     * @hide
+     */
     public static final int TYPE_VPN_PLATFORM = 2;
 
+    /**
+     * An IPsec VPN created by the built-in LegacyVpnRunner.
+     * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead.
+     * @hide
+     */
+    @Deprecated
+    public static final int TYPE_VPN_LEGACY = 3;
+
     /** @hide */
-    @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM})
+    @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
     @Retention(RetentionPolicy.SOURCE)
     public @interface VpnType {}
 
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 84dacfd..d10ff40 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -251,5 +251,5 @@
     <integer name="def_accessibility_magnification_capabilities">3</integer>
 
     <!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW -->
-    <bool name="def_enable_non_resizable_multi_window">false</bool>
+    <bool name="def_enable_non_resizable_multi_window">true</bool>
 </resources>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eab0990..1566b76 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -87,6 +87,7 @@
     <!--  TODO(b/152310230): remove once APIs are confirmed to be sufficient -->
     <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
     <uses-permission android:name="android.permission.MOVE_PACKAGE" />
+    <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
     <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
     <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
@@ -399,6 +400,7 @@
     <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+    <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
 
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
index 0831e0e..da079cf0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
@@ -103,5 +103,10 @@
         default Animator getOutAnimation() {
             return null;
         }
+
+        /**
+         * Called on orientation changes.
+         */
+        default void onOrientationChange(int orientation) {  }
     }
 }
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 35a2195..f3ec39d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -74,9 +74,7 @@
             android:layout_gravity="center_horizontal"
             android:gravity="center_horizontal"
             android:textSize="100dp"
-            android:includeFontPadding="false"
             android:fontFamily="@font/clock"
-            android:lineSpacingMultiplier=".7"
             android:typeface="monospace"
             android:elegantTextHeight="false"
             dozeWeight="200"
@@ -96,8 +94,6 @@
             android:layout_gravity="center_horizontal"
             android:gravity="center_horizontal"
             android:textSize="@dimen/large_clock_text_size"
-            android:includeFontPadding="false"
-            android:lineSpacingMultiplier=".7"
             android:fontFamily="@font/clock"
             android:typeface="monospace"
             android:elegantTextHeight="false"
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 827cf4a..109442d 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,8 +16,6 @@
 -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#242424" /> <!-- 14% of white -->
-    <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding"
-        android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding" />
+    <solid android:color="#FFFFFF" />
     <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/toast_background.xml b/packages/SystemUI/res/drawable/toast_background.xml
new file mode 100644
index 0000000..5c45e83
--- /dev/null
+++ b/packages/SystemUI/res/drawable/toast_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#FFFFFFFF" />
+    <corners android:radius="@dimen/toast_bg_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 3c30632..bad5826 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -22,19 +22,14 @@
     android:layout_height="match_parent"
     android:layout_width="wrap_content"
     android:layout_gravity="center_vertical|end"
-    android:focusable="true" >
+    android:focusable="true"
+    android:minWidth="48dp" >
 
-        <FrameLayout
-            android:id="@+id/background"
+        <LinearLayout
+            android:id="@+id/icons_container"
             android:layout_height="@dimen/ongoing_appops_chip_height"
             android:layout_width="wrap_content"
-            android:minWidth="48dp"
-            android:layout_gravity="center_vertical">
-                <LinearLayout
-                    android:id="@+id/icons_container"
-                    android:layout_height="match_parent"
-                    android:layout_width="wrap_content"
-                    android:gravity="center_vertical"
-                    />
-          </FrameLayout>
+            android:gravity="center_vertical"
+            android:layout_gravity="center"
+            />
 </com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..de4e062
--- /dev/null
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:maxWidth="@dimen/toast_width"
+    android:orientation="horizontal"
+    android:background="@drawable/toast_background"
+    android:backgroundTint="?android:attr/colorBackground"
+    android:layout_marginEnd="16dp"
+    android:layout_marginStart="16dp"
+    android:gravity="center_vertical">
+
+    <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding -->
+    <ImageView
+        android:id="@+id/icon"
+        android:alpha="@dimen/toast_icon_alpha"
+        android:padding="11.5dp"
+        android:layout_width="@dimen/toast_icon_size"
+        android:layout_height="@dimen/toast_icon_size"/>
+    <TextView
+        android:id="@+id/text"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
+        android:paddingStart="0dp"
+        android:paddingEnd="22dp"
+        android:textSize="@dimen/toast_text_size"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..24c7655 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -52,4 +52,6 @@
     <!-- (footer_height -48dp)/2 -->
     <dimen name="controls_management_footer_top_margin">4dp</dimen>
     <dimen name="controls_management_favorites_top_margin">8dp</dimen>
+
+    <dimen name="toast_y_offset">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5bb46a8..08cd655 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -163,10 +163,14 @@
     <!-- heads up elevation that is added if the view is pinned -->
     <dimen name="heads_up_pinned_elevation">16dp</dimen>
 
-    <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+    <!-- Height of a messaging notifications with actions at least. Note that this is an upper bound
          and the notification won't use this much, but is measured with wrap_content -->
     <dimen name="notification_messaging_actions_min_height">196dp</dimen>
 
+    <!-- Height of a call notification. Note that this is an upper bound
+     and the notification won't use this much, but is measured with wrap_content -->
+    <dimen name="call_notification_full_height">172dp</dimen>
+
     <!-- a threshold in dp per second that is considered fast scrolling -->
     <dimen name="scroll_fast_threshold">1500dp</dimen>
 
@@ -688,6 +692,11 @@
     <!-- The amount to shift the clocks during a small/large transition -->
     <dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
 
+    <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+    <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+
     <item name="scrim_behind_alpha" format="float" type="dimen">0.62</item>
 
     <!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
@@ -1170,17 +1179,13 @@
     <dimen name="display_cutout_margin_consumption">0px</dimen>
 
     <!-- Height of the Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_height">32dp</dimen>
-    <!-- Padding between background of Ongoing App Ops chip and content -->
-    <dimen name="ongoing_appops_chip_bg_padding">8dp</dimen>
+    <dimen name="ongoing_appops_chip_height">24dp</dimen>
     <!-- Side padding between background of Ongoing App Ops chip and content -->
     <dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
-    <!-- Margin between icons of Ongoing App Ops chip when QQS-->
-    <dimen name="ongoing_appops_chip_icon_margin_collapsed">0dp</dimen>
-    <!-- Margin between icons of Ongoing App Ops chip when QS-->
-    <dimen name="ongoing_appops_chip_icon_margin_expanded">2dp</dimen>
+    <!-- Margin between icons of Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
     <!-- Icon size of Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
+    <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen>
 
@@ -1349,4 +1354,11 @@
     <dimen name="rounded_slider_icon_size">24dp</dimen>
     <!-- rounded_slider_icon_size / 2 -->
     <dimen name="rounded_slider_icon_inset">12dp</dimen>
+
+    <dimen name="toast_width">296dp</dimen>
+    <item name="toast_icon_alpha" format="float" type="dimen">1</item>
+    <dimen name="toast_text_size">14sp</dimen>
+    <dimen name="toast_y_offset">48dp</dimen>
+    <dimen name="toast_icon_size">48dp</dimen>
+    <dimen name="toast_bg_radius">28dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index ded8a2e..6196e4a 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -39,4 +39,6 @@
 
     <!-- People Tile flag -->
     <bool name="flag_conversations">false</bool>
+
+    <bool name="flag_toast_style">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..afdf23b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -764,6 +764,7 @@
     <style name="TextAppearance.PrivacyDialog">
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="UdfpsProgressBarStyle"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7f04f28..72e4061 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -32,8 +32,29 @@
     private final Matrix mTmpTransform = new Matrix();
     private final float[] mTmpFloat9 = new float[9];
     private final RectF mTmpSourceRectF = new RectF();
+    private final RectF mTmpDestinationRectF = new RectF();
     private final Rect mTmpDestinationRect = new Rect();
 
+    public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRectF.set(destinationBounds);
+        mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+    }
+
+    public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds,
+            float degree, float positionX, float positionY) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRectF.set(destinationBounds);
+        mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        mTmpTransform.postRotate(degree, 0, 0);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setPosition(leash, positionX, positionY);
+    }
+
     public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect sourceBounds, Rect destinationBounds, Rect insets) {
         mTmpSourceRectF.set(sourceBounds);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e38cf23..5c943f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -245,9 +245,11 @@
     void setSideStageVisibility(in boolean visible) = 36;
     /** Removes the split-screen stages. */
     void exitSplitScreen() = 37;
-    void startTask(in int taskId, in int stage, in int position, in Bundle options) = 38;
+    /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38;
+    void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39;
     void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
-            in Bundle options, in UserHandle user) = 39;
+            in Bundle options, in UserHandle user) = 40;
     void startIntent(
-            in PendingIntent intent, in int stage, in int position, in Bundle options) = 40;
+            in PendingIntent intent, in int stage, in int position, in Bundle options) = 41;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 186379a..f6b239e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -43,17 +43,6 @@
 
     public static final String TAG = "Task";
 
-    /* Task callbacks */
-    @Deprecated
-    public interface TaskCallbacks {
-        /* Notifies when a task has been bound */
-        void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
-        /* Notifies when a task has been unbound */
-        void onTaskDataUnloaded();
-        /* Notifies when a task's windowing mode has changed. */
-        void onTaskWindowingModeChanged();
-    }
-
     /**
      * The Task Key represents the unique primary key for the task
      */
@@ -209,12 +198,6 @@
     public TaskKey key;
 
     /**
-     * The temporary sort index in the stack, used when ordering the stack.
-     */
-    @Deprecated
-    public int temporarySortIndexInStack;
-
-    /**
      * The icon is the task description icon (if provided), which falls back to the activity icon,
      * which can then fall back to the application icon.
      */
@@ -229,45 +212,24 @@
     public int colorPrimary;
     @ViewDebug.ExportedProperty(category="recents")
     public int colorBackground;
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean useLightOnPrimaryColor;
 
     /**
      * The task description for this task, only used to reload task icons.
      */
     public TaskDescription taskDescription;
 
-    /**
-     * The state isLaunchTarget will be set for the correct task upon launching Recents.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean isLaunchTarget;
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean isStackTask;
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean isSystemApp;
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isDockable;
 
-    /**
-     * Resize mode. See {@link ActivityInfo#resizeMode}.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public int resizeMode;
-
     @ViewDebug.ExportedProperty(category="recents")
     public ComponentName topActivity;
 
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isLocked;
 
-    @Deprecated
-    private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
+    // Last snapshot data, only used for recent tasks
+    public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+            new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
 
     public Task() {
         // Do nothing
@@ -289,6 +251,15 @@
         this.taskDescription = new TaskDescription();
     }
 
+    public Task(Task other) {
+        this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
+                other.isLocked, other.taskDescription, other.topActivity);
+    }
+
+    /**
+     * Use {@link Task#Task(Task)}.
+     */
+    @Deprecated
     public Task(TaskKey key, int colorPrimary, int colorBackground,
             boolean isDockable, boolean isLocked, TaskDescription taskDescription,
             ComponentName topActivity) {
@@ -301,103 +272,6 @@
         this.topActivity = topActivity;
     }
 
-    @Deprecated
-    public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title,
-            String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget,
-            boolean isStackTask, boolean isSystemApp, boolean isDockable,
-            TaskDescription taskDescription, int resizeMode, ComponentName topActivity,
-            boolean isLocked) {
-        this.key = key;
-        this.icon = icon;
-        this.thumbnail = thumbnail;
-        this.title = title;
-        this.titleDescription = titleDescription;
-        this.colorPrimary = colorPrimary;
-        this.colorBackground = colorBackground;
-        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
-                Color.WHITE) > 3f;
-        this.taskDescription = taskDescription;
-        this.isLaunchTarget = isLaunchTarget;
-        this.isStackTask = isStackTask;
-        this.isSystemApp = isSystemApp;
-        this.isDockable = isDockable;
-        this.resizeMode = resizeMode;
-        this.topActivity = topActivity;
-        this.isLocked = isLocked;
-    }
-
-    /**
-     * Copies the metadata from another task, but retains the current callbacks.
-     */
-    @Deprecated
-    public void copyFrom(Task o) {
-        this.key = o.key;
-        this.icon = o.icon;
-        this.thumbnail = o.thumbnail;
-        this.title = o.title;
-        this.titleDescription = o.titleDescription;
-        this.colorPrimary = o.colorPrimary;
-        this.colorBackground = o.colorBackground;
-        this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
-        this.taskDescription = o.taskDescription;
-        this.isLaunchTarget = o.isLaunchTarget;
-        this.isStackTask = o.isStackTask;
-        this.isSystemApp = o.isSystemApp;
-        this.isDockable = o.isDockable;
-        this.resizeMode = o.resizeMode;
-        this.isLocked = o.isLocked;
-        this.topActivity = o.topActivity;
-    }
-
-    /**
-     * Add a callback.
-     */
-    @Deprecated
-    public void addCallback(TaskCallbacks cb) {
-        if (!mCallbacks.contains(cb)) {
-            mCallbacks.add(cb);
-        }
-    }
-
-    /**
-     * Remove a callback.
-     */
-    @Deprecated
-    public void removeCallback(TaskCallbacks cb) {
-        mCallbacks.remove(cb);
-    }
-
-    /** Updates the task's windowing mode. */
-    @Deprecated
-    public void setWindowingMode(int windowingMode) {
-        key.setWindowingMode(windowingMode);
-        int callbackCount = mCallbacks.size();
-        for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskWindowingModeChanged();
-        }
-    }
-
-    /** Notifies the callback listeners that this task has been loaded */
-    @Deprecated
-    public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
-        this.icon = applicationIcon;
-        this.thumbnail = thumbnailData;
-        int callbackCount = mCallbacks.size();
-        for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
-        }
-    }
-
-    /** Notifies the callback listeners that this task has been unloaded */
-    @Deprecated
-    public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
-        icon = defaultApplicationIcon;
-        thumbnail = null;
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            mCallbacks.get(i).onTaskDataUnloaded();
-        }
-    }
-
     /**
      * Returns the top activity component.
      */
@@ -424,9 +298,6 @@
         if (!isDockable) {
             writer.print(" dockable=N");
         }
-        if (isLaunchTarget) {
-            writer.print(" launchTarget=Y");
-        }
         if (isLocked) {
             writer.print(" locked=Y");
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 59e81cf..0a117c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -16,37 +16,72 @@
 
 package com.android.keyguard;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.icu.text.NumberFormat;
 import android.util.MathUtils;
 
 import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.util.ViewController;
 
+import java.util.Locale;
+import java.util.Objects;
 import java.util.TimeZone;
 
 /**
- * Controls the color of a GradientTextClock.
+ * Controller for an AnimatableClockView.
  */
 public class AnimatableClockController extends ViewController<AnimatableClockView> {
+    private static final int FORMAT_NUMBER = 1234567890;
 
     private final StatusBarStateController mStatusBarStateController;
-    private final int[] mDozingColors = new int[] {Color.WHITE, Color.WHITE};
-    private int[] mLockScreenColors = new int[2];
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final int mDozingColor = Color.WHITE;
+    private int mLockScreenColor;
 
     private boolean mIsDozing;
+    private Locale mLocale;
+
+    private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
+    private final String mBurmeseNumerals;
+    private final float mBurmeseLineSpacing;
+    private final float mDefaultLineSpacing;
 
     public AnimatableClockController(
             AnimatableClockView view,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            BroadcastDispatcher broadcastDispatcher) {
         super(view);
         mStatusBarStateController = statusBarStateController;
         mIsDozing = mStatusBarStateController.isDozing();
+        mBroadcastDispatcher = broadcastDispatcher;
+
+        mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
+        mBurmeseLineSpacing = getContext().getResources().getFloat(
+                R.dimen.keyguard_clock_line_spacing_scale_burmese);
+        mDefaultLineSpacing = getContext().getResources().getFloat(
+                R.dimen.keyguard_clock_line_spacing_scale);
     }
 
+    private BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateLocale();
+        }
+    };
+
     @Override
     protected void onViewAttached() {
+        updateLocale();
+        mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mIsDozing = mStatusBarStateController.isDozing();
         refreshTime();
@@ -55,6 +90,7 @@
 
     @Override
     protected void onViewDetached() {
+        mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
     }
 
@@ -84,11 +120,23 @@
         mView.refreshFormat();
     }
 
+    private void updateLocale() {
+        Locale currLocale = Locale.getDefault();
+        if (!Objects.equals(currLocale, mLocale)) {
+            mLocale = currLocale;
+            NumberFormat nf = NumberFormat.getInstance(mLocale);
+            if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
+                mView.setLineSpacingScale(mBurmeseLineSpacing);
+            } else {
+                mView.setLineSpacingScale(mDefaultLineSpacing);
+            }
+        }
+    }
+
     private void initColors() {
-        mLockScreenColors[0] = Utils.getColorAttrDefaultColor(getContext(),
+        mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
                 com.android.systemui.R.attr.wallpaperTextColor);
-        mLockScreenColors[1] = mLockScreenColors[0]; // same color
-        mView.setColors(mDozingColors, mLockScreenColors);
+        mView.setColors(mDozingColor, mLockScreenColor);
         mView.animateDoze(mIsDozing, false);
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index ca99563..64b3d73 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -44,12 +44,13 @@
 
     private final Calendar mTime = Calendar.getInstance();
 
-    private CharSequence mFormat;
-    private CharSequence mDescFormat;
-    private int[] mDozingColors;
-    private int[] mLockScreenColors;
     private final int mDozingWeight;
     private final int mLockScreenWeight;
+    private CharSequence mFormat;
+    private CharSequence mDescFormat;
+    private int mDozingColor;
+    private int mLockScreenColor;
+    private float mLineSpacingScale = 1f;
 
     private TextAnimator mTextAnimator = null;
     private Runnable mOnTextAnimatorInitialized;
@@ -111,8 +112,7 @@
                     () -> {
                         invalidate();
                         return Unit.INSTANCE;
-                    },
-                    2 /* number of lines (each can have a unique Paint) */);
+                    });
             if (mOnTextAnimatorInitialized != null) {
                 mOnTextAnimatorInitialized.run();
                 mOnTextAnimatorInitialized = null;
@@ -127,15 +127,20 @@
         mTextAnimator.draw(canvas);
     }
 
-    void setColors(int[] dozingColors, int[] lockScreenColors) {
-        mDozingColors = dozingColors;
-        mLockScreenColors = lockScreenColors;
+    void setLineSpacingScale(float scale) {
+        mLineSpacingScale = scale;
+        setLineSpacing(0, mLineSpacingScale);
+    }
+
+    void setColors(int dozingColor, int lockScreenColor) {
+        mDozingColor = dozingColor;
+        mLockScreenColor = lockScreenColor;
     }
 
     void animateDoze(boolean isDozing, boolean animate) {
         setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
                 -1,
-                isDozing ? mDozingColors : mLockScreenColors,
+                isDozing ? mDozingColor : mLockScreenColor,
                 animate);
     }
 
@@ -152,15 +157,15 @@
     private void setTextStyle(
             @IntRange(from = 0, to = 1000) int weight,
             @FloatRange(from = 0) float textSize,
-            int[] colors,
+            int color,
             boolean animate) {
         if (mTextAnimator != null) {
-            mTextAnimator.setTextStyle(weight, textSize, colors, animate, ANIM_DURATION, null);
+            mTextAnimator.setTextStyle(weight, textSize, color, animate, ANIM_DURATION, null);
         } else {
             // when the text animator is set, update its start values
             mOnTextAnimatorInitialized =
                     () -> mTextAnimator.setTextStyle(
-                            weight, textSize, colors, false, ANIM_DURATION, null);
+                            weight, textSize, color, false, ANIM_DURATION, null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index e0de180..e375877 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -28,6 +28,7 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ClockPlugin;
@@ -56,6 +57,7 @@
     private final ClockManager mClockManager;
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final NotificationIconAreaController mNotificationIconAreaController;
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     /**
      * Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
@@ -101,7 +103,8 @@
             SysuiColorExtractor colorExtractor, ClockManager clockManager,
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
-            ContentResolver contentResolver) {
+            ContentResolver contentResolver,
+            BroadcastDispatcher broadcastDispatcher) {
         super(keyguardClockSwitch);
         mResources = resources;
         mStatusBarStateController = statusBarStateController;
@@ -109,6 +112,7 @@
         mClockManager = clockManager;
         mKeyguardSliceViewController = keyguardSliceViewController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mBroadcastDispatcher = broadcastDispatcher;
         mTimeFormat = Settings.System.getString(contentResolver, Settings.System.TIME_12_24);
     }
 
@@ -231,12 +235,14 @@
                 mNewLockScreenClockViewController =
                         new AnimatableClockController(
                                 mView.findViewById(R.id.animatable_clock_view),
-                                mStatusBarStateController);
+                                mStatusBarStateController,
+                                mBroadcastDispatcher);
                 mNewLockScreenClockViewController.init();
                 mNewLockScreenLargeClockViewController =
                         new AnimatableClockController(
                                 mView.findViewById(R.id.animatable_clock_view_large),
-                                mStatusBarStateController);
+                                mStatusBarStateController,
+                                mBroadcastDispatcher);
                 mNewLockScreenLargeClockViewController.init();
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index f2d36d1..5735a4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -54,11 +54,10 @@
  */
 class TextAnimator(
     layout: Layout,
-    private val invalidateCallback: () -> Unit,
-    private val numLines: Int = 1
+    private val invalidateCallback: () -> Unit
 ) {
     // Following two members are for mutable for testing purposes.
-    internal var textInterpolator: TextInterpolator = TextInterpolator(layout, numLines)
+    internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
     internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
         duration = DEFAULT_ANIMATION_DURATION
         addUpdateListener {
@@ -99,7 +98,7 @@
     fun setTextStyle(
         weight: Int = -1,
         textSize: Float = -1f,
-        colors: IntArray? = null,
+        color: Int? = null,
         animate: Boolean = true,
         duration: Long = -1L,
         interpolator: TimeInterpolator? = null
@@ -110,21 +109,13 @@
         }
 
         if (textSize >= 0) {
-            for (targetPaint in textInterpolator.targetPaint)
-                targetPaint.textSize = textSize
+            textInterpolator.targetPaint.textSize = textSize
         }
         if (weight >= 0) {
-            for (targetPaint in textInterpolator.targetPaint)
-                targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+            textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
         }
-        if (colors != null) {
-            require(colors.size == textInterpolator.targetPaint.size) {
-                "colors size (${colors.size}) must be the same size as" +
-                    " targetPaints size (${textInterpolator.targetPaint.size})," +
-                    " which was initialized as numLines ($numLines)"
-            }
-            for ((index, targetPaint) in textInterpolator.targetPaint.withIndex())
-                targetPaint.color = colors[index]
+        if (color != null) {
+            textInterpolator.targetPaint.color = color
         }
         textInterpolator.onTargetPaintModified()
 
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
index 0d41a2f..5d5797c 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
@@ -30,8 +30,7 @@
  * Provide text style linear interpolation for plain text.
  */
 class TextInterpolator(
-    layout: Layout,
-    lines: Int = 1
+    layout: Layout
 ) {
 
     /**
@@ -40,11 +39,9 @@
      * Once you modified the style parameters, you have to call reshapeText to recalculate base text
      * layout.
      *
-     * @return an array list of paint objects representing one paint per line of text. If this
-     * list has a smaller size than the number of lines, all extra lines will use the Paint an
-     * index 0.
+     * @return a paint object
      */
-    val basePaint = createDefaultPaint(layout.paint, lines)
+    val basePaint = TextPaint(layout.paint)
 
     /**
      * Returns target paint used for interpolation.
@@ -52,18 +49,9 @@
      * Once you modified the style parameters, you have to call reshapeText to recalculate target
      * text layout.
      *
-     * @return an array list of paint objects representing one paint per line of text. If this
-     * list has a smaller size than the number of lines, all extra lines will use the Paint an
-     * index 0.
+     * @return a paint object
      */
-    val targetPaint = createDefaultPaint(layout.paint, lines)
-
-    private fun createDefaultPaint(paint: TextPaint, lines: Int): ArrayList<TextPaint> {
-        val paintList = ArrayList<TextPaint>()
-        for (i in 0 until lines)
-            paintList.add(TextPaint(paint))
-        return paintList
-    }
+    val targetPaint = TextPaint(layout.paint)
 
     /**
      * A class represents a single font run.
@@ -102,7 +90,7 @@
     private val fontInterpolator = FontInterpolator()
 
     // Recycling object for glyph drawing. Will be extended for the longest font run if needed.
-    private val tmpDrawPaints = ArrayList<TextPaint>()
+    private val tmpDrawPaint = TextPaint()
     private var tmpPositionArray = FloatArray(20)
 
     /**
@@ -216,10 +204,10 @@
         if (progress == 0f) {
             return
         } else if (progress == 1f) {
-            updatePaint(basePaint, targetPaint)
+            basePaint.set(targetPaint)
         } else {
-            lerp(basePaint, targetPaint, progress, tmpDrawPaints)
-            updatePaint(basePaint, tmpDrawPaints)
+            lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+            basePaint.set(tmpDrawPaint)
         }
 
         lines.forEach { line ->
@@ -237,21 +225,13 @@
         progress = 0f
     }
 
-    companion object {
-        fun updatePaint(toUpdate: ArrayList<TextPaint>, newValues: ArrayList<TextPaint>) {
-            toUpdate.clear()
-            for (paint in newValues)
-                toUpdate.add(TextPaint(paint))
-        }
-    }
-
     /**
      * Draws interpolated text at the given progress.
      *
      * @param canvas a canvas.
      */
     fun draw(canvas: Canvas) {
-        lerp(basePaint, targetPaint, progress, tmpDrawPaints)
+        lerp(basePaint, targetPaint, progress, tmpDrawPaint)
         lines.forEachIndexed { lineNo, line ->
             line.runs.forEach { run ->
                 canvas.save()
@@ -261,10 +241,7 @@
                     canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
 
                     run.fontRuns.forEach { fontRun ->
-                        if (lineNo >= tmpDrawPaints.size)
-                            drawFontRun(canvas, run, fontRun, tmpDrawPaints[0])
-                        else
-                            drawFontRun(canvas, run, fontRun, tmpDrawPaints[lineNo])
+                        drawFontRun(canvas, run, fontRun, tmpDrawPaint)
                     }
                 } finally {
                     canvas.restore()
@@ -430,27 +407,19 @@
     }
 
     // Linear interpolate the paint.
-    private fun lerp(
-        from: ArrayList<TextPaint>,
-        to: ArrayList<TextPaint>,
-        progress: Float,
-        out: ArrayList<TextPaint>
-    ) {
-        out.clear()
+    private fun lerp(from: Paint, to: Paint, progress: Float, out: Paint) {
+        out.set(from)
+
         // Currently only font size & colors are interpolated.
         // TODO(172943390): Add other interpolation or support custom interpolator.
-        for (index in from.indices) {
-            val paint = TextPaint(from[index])
-            paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress)
-            paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress)
-            out.add(paint)
-        }
+        out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
+        out.color = ColorUtils.blendARGB(from.color, to.color, progress)
     }
 
     // Shape the text and stores the result to out argument.
     private fun shapeText(
         layout: Layout,
-        paints: ArrayList<TextPaint>
+        paint: TextPaint
     ): List<List<PositionedGlyphs>> {
         val out = mutableListOf<List<PositionedGlyphs>>()
         for (lineNo in 0 until layout.lineCount) { // Shape all lines.
@@ -458,7 +427,7 @@
             val count = layout.getLineEnd(lineNo) - lineStart
             val runs = mutableListOf<PositionedGlyphs>()
             TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
-                    paints[lineNo]) { _, _, glyphs, _ ->
+                    paint) { _, _, glyphs, _ ->
                 runs.add(glyphs)
             }
             out.add(runs)
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index caaee5f..1765627 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -49,7 +49,6 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.settingslib.Utils;
 import com.android.settingslib.graph.ThemedBatteryDrawable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher;
@@ -105,11 +104,6 @@
     private DualToneHandler mDualToneHandler;
     private int mUser;
 
-    /**
-     * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
-     */
-    private boolean mUseWallpaperTextColors;
-
     private int mNonAdaptedSingleToneColor;
     private int mNonAdaptedForegroundColor;
     private int mNonAdaptedBackgroundColor;
@@ -242,31 +236,6 @@
         mIsSubscribedForTunerUpdates = false;
     }
 
-    /**
-     * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
-     * revert back to dark-mode-based/tinted colors.
-     *
-     * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
-     *                                    components
-     */
-    public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
-        if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
-            return;
-        }
-
-        mUseWallpaperTextColors = shouldUseWallpaperTextColor;
-
-        if (mUseWallpaperTextColors) {
-            updateColors(
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary),
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
-        } else {
-            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
-                    mNonAdaptedSingleToneColor);
-        }
-    }
-
     public void setColorsFromContext(Context context) {
         if (context == null) {
             return;
@@ -476,13 +445,19 @@
         mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
         mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
 
-        if (!mUseWallpaperTextColors) {
-            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
-                    mNonAdaptedSingleToneColor);
-        }
+        updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
+                mNonAdaptedSingleToneColor);
     }
 
-    private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
+    /**
+     * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events,
+     * if registered.
+     *
+     * @param foregroundColor
+     * @param backgroundColor
+     * @param singleToneColor
+     */
+    public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
         mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
         mTextColor = singleToneColor;
         if (mBatteryPercentView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index 429f67f..d06568a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -24,6 +24,9 @@
  */
 interface ControlActionCoordinator {
 
+    // Handle actions launched from GlobalActionsDialog or ControlDialog
+    var startedFromGlobalActions: Boolean
+
     /**
      * Close any dialogs which may have been open
      */
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 7cd7e18..247f25e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -30,6 +30,7 @@
 import android.service.controls.actions.FloatAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsComponent
@@ -37,6 +38,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.TaskViewFactory
+import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
 
@@ -48,13 +50,17 @@
     private val activityStarter: ActivityStarter,
     private val keyguardStateController: KeyguardStateController,
     private val globalActionsComponent: GlobalActionsComponent,
-    private val taskViewFactory: Optional<TaskViewFactory>
+    private val taskViewFactory: Optional<TaskViewFactory>,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val lazyUiController: Lazy<ControlsUiController>
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
     private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
     private var pendingAction: Action? = null
     private var actionsInProgress = mutableSetOf<String>()
 
+    override var startedFromGlobalActions: Boolean = true
+
     companion object {
         private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
     }
@@ -131,8 +137,8 @@
 
     private fun bouncerOrRun(action: Action) {
         if (keyguardStateController.isShowing()) {
-            var closeGlobalActions = !keyguardStateController.isUnlocked()
-            if (closeGlobalActions) {
+            var closeDialog = !keyguardStateController.isUnlocked()
+            if (closeDialog) {
                 context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
 
                 // pending actions will only run after the control state has been refreshed
@@ -141,8 +147,12 @@
 
             activityStarter.dismissKeyguardThenExecute({
                 Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
-                if (closeGlobalActions) {
-                    globalActionsComponent.handleShowGlobalActionsMenu()
+                if (closeDialog) {
+                    if (startedFromGlobalActions) {
+                        globalActionsComponent.handleShowGlobalActionsMenu()
+                    } else {
+                        ControlsDialog(context, broadcastDispatcher).show(lazyUiController.get())
+                    }
                 } else {
                     action.invoke()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index 8e878cf..f533cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -66,7 +66,7 @@
 
         val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls)
         vg.alpha = 0f
-        controller.show(vg, { /* do nothing */ })
+        controller.show(vg, { /* do nothing */ }, false /* startedFromGlobalActions */)
 
         vg.animate()
             .alpha(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 4e4c82c..9448877 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -29,7 +29,7 @@
         public const val EXTRA_ANIMATE = "extra_animate"
     }
 
-    fun show(parent: ViewGroup, dismissGlobalActions: Runnable)
+    fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean)
     fun hide()
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 2b529f9..762362c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -102,7 +102,7 @@
     private lateinit var lastItems: List<SelectionItem>
     private var popup: ListPopupWindow? = null
     private var hidden = true
-    private lateinit var dismissGlobalActions: Runnable
+    private lateinit var onDismiss: Runnable
     private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
     private var retainCache = false
 
@@ -145,13 +145,19 @@
         }
     }
 
-    override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) {
+    override fun show(
+        parent: ViewGroup,
+        onDismiss: Runnable,
+        startedFromGlobalActions: Boolean
+    ) {
         Log.d(ControlsUiController.TAG, "show()")
         this.parent = parent
-        this.dismissGlobalActions = dismissGlobalActions
+        this.onDismiss = onDismiss
         hidden = false
         retainCache = false
 
+        controlActionCoordinator.startedFromGlobalActions = startedFromGlobalActions
+
         allStructures = controlsController.get().getFavorites()
         selectedStructure = loadPreference(allStructures)
 
@@ -187,7 +193,7 @@
                 controlViewsById.clear()
                 controlsById.clear()
 
-                show(parent, dismissGlobalActions)
+                show(parent, onDismiss, controlActionCoordinator.startedFromGlobalActions)
                 val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
                 showAnim.setInterpolator(DecelerateInterpolator(1.0f))
                 showAnim.setDuration(FADE_IN_MILLIS)
@@ -260,7 +266,7 @@
     private fun startActivity(context: Context, intent: Intent) {
         // Force animations when transitioning from a dialog to an activity
         intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
-        dismissGlobalActions.run()
+        onDismiss.run()
 
         activityStarter.dismissKeyguardThenExecute({
             shadeController.collapsePanel(false)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index ec4a91c..2b362b9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -92,4 +93,10 @@
     @IntoMap
     @ClassKey(TvNotificationPanelActivity.class)
     public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
+
+    /** Inject into PeopleSpaceActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(PeopleSpaceActivity.class)
+    public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 84dd259..f3726a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.dagger;
 
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.tv.TvWMComponent;
+import com.android.systemui.wmshell.TvWMShellModule;
 import com.android.systemui.wmshell.WMShellModule;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.ShellInit;
@@ -34,7 +39,13 @@
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for WindowManager.
+ * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
+ * from the WM component into the SysUI component (in
+ * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * provided by its particular device/form-factor SystemUI implementation.
+ *
+ * ie. {@link WMComponent} includes {@link WMShellModule}
+ *     and {@link TvWMComponent} includes {@link TvWMShellModule}
  */
 @WMSingleton
 @Subcomponent(modules = {WMShellModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ad4c447..8af45a5 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -2229,7 +2229,8 @@
 
         private void showControls(ControlsUiController controller) {
             mControlsUiController = controller;
-            mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+            mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
+                    true /* startedFromGlobalActions */);
         }
 
         private boolean isWalletViewAvailable() {
@@ -2457,7 +2458,8 @@
                 return WindowInsets.CONSUMED;
             });
             if (mControlsUiController != null) {
-                mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+                mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
+                        true /* startedFromGlobalActions */);
             }
 
             mBackgroundDrawable.setAlpha(0);
@@ -2632,7 +2634,8 @@
             initializeLayout();
             mGlobalActionsLayout.updateList();
             if (mControlsUiController != null) {
-                mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+                mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
+                        true /* startedFromGlobalActions */);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index d0070d8d..8c04143 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -289,7 +289,8 @@
         if (hasIndications()) {
             pw.println("    All messages:");
             for (int type : mIndicationMessages.keySet()) {
-                pw.println("        type=" + type + " message=" + mIndicationMessages.get(type));
+                pw.println("        type=" + type
+                        + " message=" + mIndicationMessages.get(type).getMessage());
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5a918d4..c55fdf4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -67,6 +67,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.RemoteAnimationTarget;
@@ -306,6 +307,13 @@
      */
     private final SparseIntArray mLastSimStates = new SparseIntArray();
 
+    /**
+     * Indicates if a SIM card had the SIM PIN enabled during the initialization, before
+     * reaching the SIM_STATE_READY state. The flag is reset to false at SIM_STATE_READY.
+     * Index is the slotId - in case of multiple SIM cards.
+     */
+    private final SparseBooleanArray mSimWasLocked = new SparseBooleanArray();
+
     private boolean mDeviceInteractive;
     private boolean mGoingToSleep;
 
@@ -477,10 +485,10 @@
                 }
             }
 
-            boolean simWasLocked;
+            boolean lastSimStateWasLocked;
             synchronized (KeyguardViewMediator.this) {
                 int lastState = mLastSimStates.get(slotId);
-                simWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
+                lastSimStateWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
                         || lastState == TelephonyManager.SIM_STATE_PUK_REQUIRED);
                 mLastSimStates.append(slotId, simState);
             }
@@ -504,17 +512,19 @@
                         if (simState == TelephonyManager.SIM_STATE_ABSENT) {
                             // MVNO SIMs can become transiently NOT_READY when switching networks,
                             // so we should only lock when they are ABSENT.
-                            if (simWasLocked) {
+                            if (lastSimStateWasLocked) {
                                 if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
                                         + "previous state was locked. Reset the state.");
                                 resetStateLocked();
                             }
+                            mSimWasLocked.append(slotId, false);
                         }
                     }
                     break;
                 case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                 case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                     synchronized (KeyguardViewMediator.this) {
+                        mSimWasLocked.append(slotId, true);
                         if (!mShowing) {
                             if (DEBUG_SIM_STATES) Log.d(TAG,
                                     "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
@@ -541,9 +551,10 @@
                 case TelephonyManager.SIM_STATE_READY:
                     synchronized (KeyguardViewMediator.this) {
                         if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
-                        if (mShowing && simWasLocked) {
+                        if (mShowing && mSimWasLocked.get(slotId, false)) {
                             if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the "
-                                    + "previous state was locked. Reset the state.");
+                                    + "previously was locked. Reset the state.");
+                            mSimWasLocked.append(slotId, false);
                             resetStateLocked();
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 4c96de2..553b6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -163,7 +163,8 @@
         }
 
         @Override
-        public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
+        public void setPlaybackProperties(IBinder token, float volume, boolean looping,
+                boolean hapticGeneratorEnabled) {
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
@@ -171,6 +172,7 @@
             if (client != null) {
                 client.mRingtone.setVolume(volume);
                 client.mRingtone.setLooping(looping);
+                client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
             }
             // else no client for token when setting playback properties but will be set at play()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 580cbcf..c67aef6 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -37,9 +37,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Shows the user their tiles for their priority People (go/live-status).
  */
@@ -54,10 +57,17 @@
     private LauncherApps mLauncherApps;
     private Context mContext;
     private AppWidgetManager mAppWidgetManager;
+    private NotificationEntryManager mNotificationEntryManager;
     private int mAppWidgetId;
     private boolean mShowSingleConversation;
     private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
 
+    @Inject
+    public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) {
+        super();
+        mNotificationEntryManager = notificationEntryManager;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -91,8 +101,8 @@
      */
     private void setTileViewsWithPriorityConversations() {
         try {
-            List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(
-                    mContext, mNotificationManager, mPeopleManager, mLauncherApps);
+            List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
+                    mPeopleManager, mLauncherApps, mNotificationEntryManager);
             for (PeopleSpaceTile tile : tiles) {
                 PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext, mPeopleSpaceLayout,
                         tile.getId());
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 994dc6d..dd05484 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -60,9 +60,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ArrayUtils;
 import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.text.SimpleDateFormat;
 import java.time.Duration;
@@ -137,7 +140,7 @@
     /** Returns a list of map entries corresponding to user's conversations. */
     public static List<PeopleSpaceTile> getTiles(
             Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
-            LauncherApps launcherApps)
+            LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
             throws Exception {
         boolean showOnlyPriority = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 1;
@@ -173,6 +176,8 @@
                     getSortedTiles(peopleManager, launcherApps, mergedStream);
             tiles.addAll(recentTiles);
         }
+
+        tiles = augmentTilesFromVisibleNotifications(tiles, notificationEntryManager);
         return tiles;
     }
 
@@ -258,7 +263,8 @@
                             ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
                             IPeopleManager.Stub.asInterface(
                                     ServiceManager.getService(Context.PEOPLE_SERVICE)),
-                            context.getSystemService(LauncherApps.class));
+                            context.getSystemService(LauncherApps.class),
+                            Dependency.get(NotificationEntryManager.class));
             Optional<PeopleSpaceTile> entry = tiles.stream().filter(
                     e -> e.getId().equals(shortcutId)).findFirst();
             if (entry.isPresent()) {
@@ -339,6 +345,41 @@
                 && storedUserId == userId;
     }
 
+    static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(List<PeopleSpaceTile> tiles,
+            NotificationEntryManager notificationEntryManager) {
+        if (notificationEntryManager == null) {
+            Log.w(TAG, "NotificationEntryManager is null");
+            return tiles;
+        }
+        Map<String, NotificationEntry> visibleNotifications = notificationEntryManager
+                .getVisibleNotifications()
+                .stream()
+                .filter(entry -> entry.getRanking() != null
+                        && entry.getRanking().getConversationShortcutInfo() != null)
+                .collect(Collectors.toMap(PeopleSpaceUtils::getKey, e -> e));
+        if (DEBUG) {
+            Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size());
+        }
+        return tiles
+                .stream()
+                .map(entry -> augmentTileFromVisibleNotifications(entry, visibleNotifications))
+                .collect(Collectors.toList());
+    }
+
+    static PeopleSpaceTile augmentTileFromVisibleNotifications(PeopleSpaceTile tile,
+            Map<String, NotificationEntry> visibleNotifications) {
+        String shortcutId = tile.getId();
+        String packageName = tile.getPackageName();
+        int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+        String key = getKey(shortcutId, packageName, userId);
+        if (!visibleNotifications.containsKey(key)) {
+            if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key);
+            return tile;
+        }
+        if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key);
+        return augmentTileFromNotification(tile, visibleNotifications.get(key).getSbn());
+    }
+
     /**
      * If incoming notification changed tile, store the changes in the tile options.
      */
@@ -355,17 +396,7 @@
         }
         if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
             if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId);
-            Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn);
-            if (message == null) {
-                if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping.");
-                return;
-            }
-            storedTile = storedTile
-                    .toBuilder()
-                    .setNotificationKey(sbn.getKey())
-                    .setNotificationContent(message.getText())
-                    .setNotificationDataUri(message.getDataUri())
-                    .build();
+            storedTile = augmentTileFromNotification(storedTile, sbn);
         } else {
             if (DEBUG) {
                 Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId);
@@ -380,6 +411,21 @@
         updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile);
     }
 
+    static PeopleSpaceTile augmentTileFromNotification(PeopleSpaceTile tile,
+            StatusBarNotification sbn) {
+        Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn);
+        if (message == null) {
+            if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping.");
+            return tile;
+        }
+        return tile
+                .toBuilder()
+                .setNotificationKey(sbn.getKey())
+                .setNotificationContent(message.getText())
+                .setNotificationDataUri(message.getDataUri())
+                .build();
+    }
+
     private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId,
             PeopleSpaceTile tile) {
         if (tile == null) {
@@ -792,6 +838,16 @@
         return lookupKeysWithBirthdaysToday;
     }
 
+    static String getKey(NotificationEntry entry) {
+        if (entry.getRanking() == null || entry.getRanking().getConversationShortcutInfo() == null
+                || entry.getSbn() == null || entry.getSbn().getUser() == null) {
+            return null;
+        }
+        return getKey(entry.getRanking().getConversationShortcutInfo().getId(),
+                entry.getSbn().getPackageName(),
+                entry.getSbn().getUser().getIdentifier());
+    }
+
     /**
      * Returns the uniquely identifying key for the conversation.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index bee54e4..bee9889 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -135,12 +135,14 @@
         try {
             String sbnShortcutId = sbn.getShortcutId();
             if (sbnShortcutId == null) {
+                if (DEBUG) Log.d(TAG, "Sbn shortcut id is null");
                 return;
             }
             int[] widgetIds = mAppWidgetService.getAppWidgetIds(
                     new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
             );
             if (widgetIds.length == 0) {
+                Log.d(TAG, "No app widget ids returned");
                 return;
             }
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -148,6 +150,7 @@
             String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId);
             Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
             if (storedWidgetIds.isEmpty()) {
+                Log.d(TAG, "No stored widget ids");
                 return;
             }
             for (String widgetIdString : storedWidgetIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
index fb33aff..80794cb 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
@@ -28,9 +28,11 @@
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.people.PeopleSpaceTileView;
 import com.android.systemui.people.PeopleSpaceUtils;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -42,6 +44,7 @@
 
     private IPeopleManager mPeopleManager;
     private INotificationManager mNotificationManager;
+    private NotificationEntryManager mNotificationEntryManager;
     private PackageManager mPackageManager;
     private LauncherApps mLauncherApps;
     private List<PeopleSpaceTile> mTiles = new ArrayList<>();
@@ -56,6 +59,7 @@
         if (DEBUG) Log.d(TAG, "onCreate called");
         mNotificationManager = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mPackageManager = mContext.getPackageManager();
         mPeopleManager = IPeopleManager.Stub.asInterface(
                 ServiceManager.getService(Context.PEOPLE_SERVICE));
@@ -70,7 +74,7 @@
     private void setTileViewsWithPriorityConversations() {
         try {
             mTiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
-                    mPeopleManager, mLauncherApps);
+                    mPeopleManager, mLauncherApps, mNotificationEntryManager);
         } catch (Exception e) {
             Log.e(TAG, "Couldn't retrieve conversations", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 870e714..bdd37fc 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -16,11 +16,11 @@
 
 import android.content.Context
 import android.util.AttributeSet
-import android.view.Gravity
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
+import com.android.settingslib.Utils
 import com.android.systemui.R
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -30,26 +30,13 @@
     defStyleRes: Int = 0
 ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
 
-    private val iconMarginExpanded = context.resources.getDimensionPixelSize(
-                    R.dimen.ongoing_appops_chip_icon_margin_expanded)
-    private val iconMarginCollapsed = context.resources.getDimensionPixelSize(
-                    R.dimen.ongoing_appops_chip_icon_margin_collapsed)
-    private val iconSize =
-            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
-    private val iconColor = context.resources.getColor(
-            R.color.status_bar_clock_color, context.theme)
-    private val sidePadding =
-            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
-    private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg)
+    private var iconMargin = 0
+    private var iconSize = 0
+    private var iconColor = 0
+    private var defaultBackgroundColor = 0
+    private var cameraBackgroundColor = 0
+
     private lateinit var iconsContainer: LinearLayout
-    private lateinit var back: FrameLayout
-    var expanded = false
-        set(value) {
-            if (value != field) {
-                field = value
-                updateView(PrivacyChipBuilder(context, privacyList))
-            }
-        }
 
     var privacyList = emptyList<PrivacyItem>()
         set(value) {
@@ -60,15 +47,13 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
 
-        back = requireViewById(R.id.background)
         iconsContainer = requireViewById(R.id.icons_container)
+
+        updateResources()
     }
 
     // Should only be called if the builder icons or app changed
     private fun updateView(builder: PrivacyChipBuilder) {
-        back.background = if (expanded) backgroundDrawable else null
-        val padding = if (expanded) sidePadding else 0
-        back.setPaddingRelative(padding, 0, padding, 0)
         fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) {
             iconsContainer.removeAllViews()
             chipBuilder.generateIcons().forEachIndexed { i, it ->
@@ -81,7 +66,7 @@
                 iconsContainer.addView(image, iconSize, iconSize)
                 if (i != 0) {
                     val lp = image.layoutParams as MarginLayoutParams
-                    lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed
+                    lp.marginStart = iconMargin
                     image.layoutParams = lp
                 }
             }
@@ -90,10 +75,11 @@
         if (!privacyList.isEmpty()) {
             generateContentDescription(builder)
             setIcons(builder, iconsContainer)
-            val lp = iconsContainer.layoutParams as FrameLayout.LayoutParams
-            lp.gravity = Gravity.CENTER_VERTICAL or
-                    (if (expanded) Gravity.CENTER_HORIZONTAL else Gravity.END)
-            iconsContainer.layoutParams = lp
+            if (builder.types.contains(PrivacyType.TYPE_CAMERA)) {
+                iconsContainer.background.setTint(cameraBackgroundColor)
+            } else {
+                iconsContainer.background.setTint(defaultBackgroundColor)
+            }
         } else {
             iconsContainer.removeAllViews()
         }
@@ -105,4 +91,20 @@
         contentDescription = context.getString(
                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
     }
+
+    private fun updateResources() {
+        iconMargin = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+        iconSize = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+        iconColor =
+                Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+        defaultBackgroundColor = context.getColor(R.color.privacy_circle_microphone_location)
+        cameraBackgroundColor = context.getColor(R.color.privacy_circle_camera)
+
+        val padding = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+        iconsContainer.setPaddingRelative(padding, 0, padding, 0)
+        iconsContainer.background = context.getDrawable(R.drawable.privacy_chip_bg)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index d248ab5..c8edaec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -362,7 +362,7 @@
         if(view == parent || view == null) return;
         // Ignore tile pages as they can have some offset we don't want to take into account in
         // RTL.
-        if (!(view instanceof PagedTileLayout.TilePage)) {
+        if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) {
             loc1[0] += view.getLeft();
             loc1[1] += view.getTop();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4248cf2..87252ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -29,7 +29,6 @@
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.util.Pair;
-import android.view.ContextThemeWrapper;
 import android.view.DisplayCutout;
 import android.view.View;
 import android.view.ViewGroup;
@@ -48,7 +47,6 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
-import com.android.systemui.DualToneHandler;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.privacy.OngoingPrivacyChip;
@@ -75,8 +73,6 @@
     protected QuickQSPanel mHeaderQsPanel;
     private TouchAnimator mStatusIconsAlphaAnimator;
     private TouchAnimator mHeaderTextContainerAlphaAnimator;
-    private TouchAnimator mPrivacyChipAlphaAnimator;
-    private DualToneHandler mDualToneHandler;
 
     private View mSystemIconsView;
     private View mQuickQsStatusIcons;
@@ -111,8 +107,6 @@
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDualToneHandler = new DualToneHandler(
-                new ContextThemeWrapper(context, R.style.QSHeaderTheme));
     }
 
     @Override
@@ -150,10 +144,8 @@
     }
 
     void onAttach(TintedIconManager iconManager) {
-        int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
-                android.R.attr.colorForeground);
-        float intensity = getColorIntensity(colorForeground);
-        int fillColor = mDualToneHandler.getSingleColor(intensity);
+        int fillColor = Utils.getColorAttrDefaultColor(getContext(),
+                android.R.attr.textColorPrimary);
 
         // Set the correct tint for the status icons so they contrast
         iconManager.setTint(fillColor);
@@ -272,19 +264,16 @@
 
         int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
         if (textColor != mTextColorPrimary) {
+            int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
+                    android.R.attr.textColorSecondary);
             mTextColorPrimary = textColor;
             mClockView.setTextColor(textColor);
-
-            float intensity = getColorIntensity(textColor);
-            int fillColor = mDualToneHandler.getSingleColor(intensity);
-
-            Rect tintArea = new Rect(0, 0, 0, 0);
-            mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor);
+            mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
+                    mTextColorPrimary);
         }
 
         updateStatusIconAlphaAnimator();
         updateHeaderTextContainerAlphaAnimator();
-        updatePrivacyChipAlphaAnimator();
     }
 
     private void updateStatusIconAlphaAnimator() {
@@ -299,12 +288,6 @@
                 .build();
     }
 
-    private void updatePrivacyChipAlphaAnimator() {
-        mPrivacyChipAlphaAnimator = new TouchAnimator.Builder()
-                .addFloat(mPrivacyChip, "alpha", 1, 0, 1)
-                .build();
-    }
-
     /** */
     public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
         if (mExpanded == expanded) return;
@@ -344,10 +327,6 @@
                 mHeaderTextContainerView.setVisibility(INVISIBLE);
             }
         }
-        if (mPrivacyChipAlphaAnimator != null) {
-            mPrivacyChip.setExpanded(expansionFraction > 0.5);
-            mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
-        }
 
         mKeyguardExpansionFraction = keyguardExpansionFraction;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 7d8d86f..eddcf8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -28,9 +28,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
-import com.android.systemui.qs.QuickStatusBarHeader;
 
 import java.util.Objects;
 
@@ -40,9 +38,6 @@
     private TextView mCarrierText;
     private ImageView mMobileSignal;
     private ImageView mMobileRoaming;
-    private DualToneHandler mDualToneHandler;
-    private ColorStateList mColorForegroundStateList;
-    private float mColorForegroundIntensity;
     private CellSignalState mLastSignalState;
 
     public QSCarrier(Context context) {
@@ -64,7 +59,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mDualToneHandler = new DualToneHandler(getContext());
         mMobileGroup = findViewById(R.id.mobile_combo);
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
             mMobileRoaming = findViewById(R.id.mobile_roaming_large);
@@ -74,11 +68,6 @@
         mMobileSignal = findViewById(R.id.mobile_signal);
         mCarrierText = findViewById(R.id.qs_carrier_text);
         mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
-
-        int colorForeground = Utils.getColorAttrDefaultColor(mContext,
-                android.R.attr.colorForeground);
-        mColorForegroundStateList = ColorStateList.valueOf(colorForeground);
-        mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
     }
 
     /**
@@ -92,8 +81,8 @@
         mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
         if (state.visible) {
             mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-            ColorStateList colorStateList = ColorStateList.valueOf(
-                    mDualToneHandler.getSingleColor(mColorForegroundIntensity));
+            ColorStateList colorStateList = Utils.getColorAttr(mContext,
+                    android.R.attr.textColorPrimary);
             mMobileRoaming.setImageTintList(colorStateList);
             mMobileSignal.setImageTintList(colorStateList);
             mMobileSignal.setImageLevel(state.mobileSignalIconId);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 01a8c1c..5b2a7e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -620,6 +620,19 @@
         }
 
         @Override
+        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+            if (!verifyCaller("exitSplitScreenOnHide")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void startTask(int taskId, int stage, int position, Bundle options) {
             if (!verifyCaller("startTask")) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 5438743..26781f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -277,12 +277,12 @@
      */
     void end() {
         mMediaRecorder.stop();
-        mMediaProjection.stop();
         mMediaRecorder.release();
-        mMediaRecorder = null;
-        mMediaProjection = null;
         mInputSurface.release();
         mVirtualDisplay.release();
+        mMediaProjection.stop();
+        mMediaRecorder = null;
+        mMediaProjection = null;
         stopInternalAudioRecording();
 
         Log.d(TAG, "end recording");
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 43bb343..0bfc8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -41,7 +41,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2dd85e9..778f813b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -74,4 +74,8 @@
     public boolean isPeopleTileEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
+
+    public boolean isToastStyleEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_toast_style);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a03fc13..845d321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -71,6 +71,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.CallLayout;
 import com.android.internal.widget.MessagingLayout;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
@@ -165,6 +166,7 @@
     private int mMaxSmallHeightLarge;
     private int mMaxSmallHeightMedia;
     private int mMaxExpandedHeight;
+    private int mMaxCallHeight;
     private int mIncreasedPaddingBetweenElements;
     private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
@@ -645,8 +647,9 @@
     }
 
     private void updateLimitsForView(NotificationContentView layout) {
-        boolean customView = layout.getContractedChild() != null
-                && layout.getContractedChild().getId()
+        View contractedView = layout.getContractedChild();
+        boolean customView = contractedView != null
+                && contractedView.getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
         boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
@@ -661,7 +664,8 @@
         View expandedView = layout.getExpandedChild();
         boolean isMediaLayout = expandedView != null
                 && expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
-        boolean isMessagingLayout = layout.getContractedChild() instanceof MessagingLayout;
+        boolean isMessagingLayout = contractedView instanceof MessagingLayout;
+        boolean isCallLayout = contractedView instanceof CallLayout;
         boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
 
         if (customView && beforeS && !mIsSummaryWithChildren) {
@@ -684,6 +688,8 @@
             //  make sure we don't crop them terribly.  We actually need to revisit this and give
             //  them a headerless design, then remove this hack.
             smallHeight = mMaxSmallHeightLarge;
+        } else if (isCallLayout) {
+            smallHeight = mMaxCallHeight;
         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
             smallHeight = mMaxSmallHeightLarge;
         } else {
@@ -1645,6 +1651,8 @@
                 R.dimen.notification_min_height_media);
         mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_height);
+        mMaxCallHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.call_notification_full_height);
         mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
         mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
new file mode 100644
index 0000000..4541ebf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 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.statusbar.notification.row.wrapper
+
+import android.content.Context
+import android.view.View
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.CallLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/**
+ * Wraps a notification containing a call template
+ */
+class NotificationCallTemplateViewWrapper constructor(
+    ctx: Context,
+    view: View,
+    row: ExpandableNotificationRow
+) : NotificationTemplateViewWrapper(ctx, view, row) {
+
+    private val minHeightWithActions: Int =
+            NotificationUtils.getFontScaledHeight(ctx, R.dimen.call_notification_full_height)
+    private val callLayout: CallLayout = view as CallLayout
+
+    private lateinit var conversationIconView: CachingIconView
+    private lateinit var conversationBadgeBg: View
+    private lateinit var expandBtn: View
+    private lateinit var appName: View
+    private lateinit var conversationTitleView: View
+
+    private fun resolveViews() {
+        with(callLayout) {
+            conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
+            conversationBadgeBg =
+                    requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
+            expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+            appName = requireViewById(com.android.internal.R.id.app_name_text)
+            conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+        }
+    }
+
+    override fun onContentUpdated(row: ExpandableNotificationRow) {
+        // Reinspect the notification. Before the super call, because the super call also updates
+        // the transformation types and we need to have our values set by then.
+        resolveViews()
+        super.onContentUpdated(row)
+    }
+
+    override fun updateTransformedTypes() {
+        // This also clears the existing types
+        super.updateTransformedTypes()
+        addTransformedViews(
+                appName,
+                conversationTitleView
+        )
+        addViewsTransformingToSimilar(
+                conversationIconView,
+                conversationBadgeBg,
+                expandBtn
+        )
+    }
+
+    override fun disallowSingleClick(x: Float, y: Float): Boolean {
+        val isOnExpandButton = expandBtn.visibility == View.VISIBLE &&
+                isOnView(expandBtn, x, y)
+        return isOnExpandButton || super.disallowSingleClick(x, y)
+    }
+
+    override fun getMinLayoutHeight(): Int = minHeightWithActions
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index c49f6cb..905bccf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.view.View
-import android.view.View.GONE
 import android.view.ViewGroup
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
@@ -48,8 +47,8 @@
 
     private lateinit var conversationIconView: CachingIconView
     private lateinit var conversationBadgeBg: View
-    private lateinit var expandButton: View
-    private lateinit var expandButtonContainer: View
+    private lateinit var expandBtn: View
+    private lateinit var expandBtnContainer: View
     private lateinit var imageMessageContainer: ViewGroup
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
@@ -66,9 +65,8 @@
             conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
             conversationBadgeBg =
                     requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
-            expandButton = requireViewById(com.android.internal.R.id.expand_button)
-            expandButtonContainer =
-                    requireViewById(com.android.internal.R.id.expand_button_container)
+            expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+            expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container)
             importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
             appName = requireViewById(com.android.internal.R.id.app_name_text)
             conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
@@ -126,7 +124,7 @@
         addViewsTransformingToSimilar(
                 conversationIconView,
                 conversationBadgeBg,
-                expandButton,
+                expandBtn,
                 importanceRing,
                 facePileTop,
                 facePileBottom,
@@ -134,11 +132,9 @@
         )
     }
 
-    override fun getExpandButton() = super.getExpandButton()
-
     override fun setShelfIconVisible(visible: Boolean) {
         if (conversationLayout.isImportantConversation) {
-            if (conversationIconView.visibility != GONE) {
+            if (conversationIconView.visibility != View.GONE) {
                 conversationIconView.isForceHidden = visible
                 // We don't want the small icon to be hidden by the extended wrapper, as force
                 // hiding the conversationIcon will already do that via its listener.
@@ -152,7 +148,7 @@
 
     override fun getShelfTransformationTarget(): View? =
             if (conversationLayout.isImportantConversation)
-                if (conversationIconView.visibility != GONE)
+                if (conversationIconView.visibility != View.GONE)
                     conversationIconView
                 else
                     // A notification with a fallback icon was set to important. Currently
@@ -169,8 +165,8 @@
             conversationLayout.updateExpandability(expandable, onClickListener)
 
     override fun disallowSingleClick(x: Float, y: Float): Boolean {
-        val isOnExpandButton = expandButtonContainer.visibility == View.VISIBLE &&
-                isOnView(expandButtonContainer, x, y)
+        val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
+                isOnView(expandBtnContainer, x, y)
         return isOnExpandButton || super.disallowSingleClick(x, y)
     }
 
@@ -179,10 +175,4 @@
                 minHeightWithActions
             else
                 super.getMinLayoutHeight()
-
-    private fun addTransformedViews(vararg vs: View?) =
-            vs.forEach { view -> view?.let(mTransformationHelper::addTransformedView) }
-
-    private fun addViewsTransformingToSimilar(vararg vs: View?) =
-            vs.forEach { view -> view?.let(mTransformationHelper::addViewTransformingToSimilar) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 97201f5..34bc537 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.NotificationExpandButton;
-import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
@@ -60,6 +59,7 @@
     private CachingIconView mIcon;
     private NotificationExpandButton mExpandButton;
     private View mAltExpandTarget;
+    private View mIconContainer;
     protected NotificationHeaderView mNotificationHeader;
     protected NotificationTopLineView mNotificationTopLine;
     private TextView mHeaderText;
@@ -112,6 +112,7 @@
         mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
         mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
+        mIconContainer = mView.findViewById(com.android.internal.R.id.conversation_icon_container);
         mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon);
         mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
@@ -203,11 +204,8 @@
     public void clearConversationSkin() {
         if (mAppNameText != null) {
             final ColorStateList colors = mAppNameText.getTextColors();
-            final int textAppearance = Utils.getThemeAttr(
-                    mAppNameText.getContext(),
-                    com.android.internal.R.attr.notificationHeaderTextAppearance,
+            mAppNameText.setTextAppearance(
                     com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
-            mAppNameText.setTextAppearance(textAppearance);
             mAppNameText.setTextColor(colors);
             MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
             final int marginStart = mAppNameText.getResources().getDimensionPixelSize(
@@ -265,19 +263,11 @@
         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER,
                 mExpandButton);
-        if (mWorkProfileImage != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
-        }
         if (mIsLowPriority && mHeaderText != null) {
             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
                     mHeaderText);
         }
-        if (mAudiblyAlertedIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
-        }
-        if (mFeedbackIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mFeedbackIcon);
-        }
+        addViewsTransformingToSimilar(mWorkProfileImage, mAudiblyAlertedIcon, mFeedbackIcon);
     }
 
     @Override
@@ -287,6 +277,9 @@
         if (mAltExpandTarget != null) {
             mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
         }
+        if (mIconContainer != null) {
+            mIconContainer.setOnClickListener(expandable ? onClickListener : null);
+        }
         if (mNotificationHeader != null) {
             mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
         }
@@ -371,4 +364,20 @@
         super.setVisible(visible);
         mTransformationHelper.setVisible(visible);
     }
+
+    protected void addTransformedViews(View... views) {
+        for (View view : views) {
+            if (view != null) {
+                mTransformationHelper.addTransformedView(view);
+            }
+        }
+    }
+
+    protected void addViewsTransformingToSimilar(View... views) {
+        for (View view : views) {
+            if (view != null) {
+                mTransformationHelper.addViewTransformingToSimilar(view);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index b3d1a94..5fff8c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -74,6 +74,8 @@
                 return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
             } else if ("conversation".equals(v.getTag())) {
                 return new NotificationConversationTemplateViewWrapper(ctx, v, row);
+            } else if ("call".equals(v.getTag())) {
+                return new NotificationCallTemplateViewWrapper(ctx, v, row);
             }
             Class<? extends Notification.Style> style =
                     row.getEntry().getSbn().getNotification().getNotificationStyle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4f4a504..738cab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -31,12 +31,11 @@
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
-import android.net.IConnectivityManager;
 import android.net.Network;
 import android.net.NetworkRequest;
+import android.net.VpnManager;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.security.KeyChain;
@@ -84,7 +83,7 @@
 
     private final Context mContext;
     private final ConnectivityManager mConnectivityManager;
-    private final IConnectivityManager mConnectivityManagerService;
+    private final VpnManager mVpnManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
@@ -116,8 +115,7 @@
                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mConnectivityManager = (ConnectivityManager)
                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
-                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+        mVpnManager = context.getSystemService(VpnManager.class);
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mBgExecutor = bgExecutor;
@@ -399,25 +397,19 @@
     private void updateState() {
         // Find all users with an active VPN
         SparseArray<VpnConfig> vpns = new SparseArray<>();
-        try {
-            for (UserInfo user : mUserManager.getUsers()) {
-                VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
-                if (cfg == null) {
+        for (UserInfo user : mUserManager.getUsers()) {
+            VpnConfig cfg = mVpnManager.getVpnConfig(user.id);
+            if (cfg == null) {
+                continue;
+            } else if (cfg.legacy) {
+                // Legacy VPNs should do nothing if the network is disconnected. Third-party
+                // VPN warnings need to continue as traffic can still go to the app.
+                LegacyVpnInfo legacyVpn = mVpnManager.getLegacyVpnInfo(user.id);
+                if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
                     continue;
-                } else if (cfg.legacy) {
-                    // Legacy VPNs should do nothing if the network is disconnected. Third-party
-                    // VPN warnings need to continue as traffic can still go to the app.
-                    LegacyVpnInfo legacyVpn = mConnectivityManagerService.getLegacyVpnInfo(user.id);
-                    if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
-                        continue;
-                    }
                 }
-                vpns.put(user.id, cfg);
             }
-        } catch (RemoteException rme) {
-            // Roll back to previous state
-            Log.e(TAG, "Unable to list active VPNs", rme);
-            return;
+            vpns.put(user.id, cfg);
         }
         mCurrentVpns = vpns;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 0552396..68d74ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -113,7 +113,9 @@
     private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
     private boolean mResumeUserOnGuestLogout = true;
     private boolean mSimpleUserSwitcher;
-    private boolean mAddUsersWhenLocked;
+    // When false, there won't be any visual affordance to add a new user from the keyguard even if
+    // the user is unlocked
+    private boolean mAddUsersFromLockScreen;
     private boolean mPauseRefreshUsers;
     private int mSecondaryUser = UserHandle.USER_NULL;
     private Intent mSecondaryUserServiceIntent;
@@ -204,7 +206,7 @@
         }
         mForcePictureLoadForUserId.clear();
 
-        final boolean addUsersWhenLocked = mAddUsersWhenLocked;
+        final boolean addUsersWhenLocked = mAddUsersFromLockScreen;
         new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
             @SuppressWarnings("unchecked")
             @Override
@@ -554,7 +556,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
         public void onChange(boolean selfChange) {
             mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-            mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
+            mAddUsersFromLockScreen = Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
             refreshUsers(UserHandle.USER_NULL);
         };
@@ -610,43 +612,26 @@
         }
 
         public int getUserCount() {
-            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
-                    && mKeyguardStateController.isMethodSecure()
-                    && !mKeyguardStateController.canDismissLockScreen();
-            if (!secureKeyguardShowing) {
-                return getUsers().size();
-            }
-            // The lock screen is secure and showing. Filter out restricted records.
-            final int userSize = getUsers().size();
-            int count = 0;
-            for (int i = 0; i < userSize; i++) {
-                if (getUsers().get(i).isGuest) continue;
-                if (getUsers().get(i).isRestricted) {
-                    break;
-                } else {
-                    count++;
-                }
-            }
-            return count;
+            return countUsers(false);
         }
 
         @Override
         public int getCount() {
-            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
-                    && mKeyguardStateController.isMethodSecure()
-                    && !mKeyguardStateController.canDismissLockScreen();
-            if (!secureKeyguardShowing) {
-                return getUsers().size();
-            }
-            // The lock screen is secure and showing. Filter out restricted records.
+            return countUsers(true);
+        }
+
+        private int countUsers(boolean includeGuest) {
+            boolean keyguardShowing = mKeyguardStateController.isShowing();
             final int userSize = getUsers().size();
             int count = 0;
             for (int i = 0; i < userSize; i++) {
-                if (getUsers().get(i).isRestricted) {
-                    break;
-                } else {
-                    count++;
+                if (getUsers().get(i).isGuest && !includeGuest) {
+                    continue;
                 }
+                if (getUsers().get(i).isRestricted && keyguardShowing) {
+                    break;
+                }
+                count++;
             }
             return count;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e357577..7863914 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,12 +15,15 @@
  */
 package com.android.systemui.theme;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -229,49 +232,53 @@
         final List<OverlayInfo> overlays = new ArrayList<>();
         targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
                 .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
-        final Map<String, String> overlaysToDisable = overlays.stream()
+        final List<Pair<String, String>> overlaysToDisable = overlays.stream()
                 .filter(o ->
                         mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
                 .filter(o -> overlayCategoriesToDisable.contains(o.category))
                 .filter(o -> o.isEnabled())
-                .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName));
+                .map(o -> new Pair<>(o.category, o.packageName))
+                .collect(Collectors.toList());
 
+        OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
         // Toggle overlays in the order of THEME_CATEGORIES.
         for (String category : THEME_CATEGORIES) {
             if (categoryToPackage.containsKey(category)) {
-                setEnabled(categoryToPackage.get(category), category, userHandles, true);
-            } else if (overlaysToDisable.containsKey(category)) {
-                setEnabled(overlaysToDisable.get(category), category, userHandles, false);
+                OverlayIdentifier overlayInfo =
+                        new OverlayIdentifier(categoryToPackage.get(category));
+                setEnabled(transaction, overlayInfo, category, userHandles, true);
             }
         }
+        for (Pair<String, String> packageToDisable : overlaysToDisable) {
+            OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+            setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false);
+        }
+
+        mExecutor.execute(() -> {
+            mOverlayManager.commit(transaction.build());
+        });
     }
 
-    private void setEnabled(
-            String packageName, String category, Set<UserHandle> handles, boolean enabled) {
+    @VisibleForTesting
+    protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+        return new OverlayManagerTransaction.Builder();
+    }
+
+    private void setEnabled(OverlayManagerTransaction.Builder transaction,
+            OverlayIdentifier identifier, String category, Set<UserHandle> handles,
+            boolean enabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: "
+                    + category + ": " + enabled);
+        }
         for (UserHandle userHandle : handles) {
-            setEnabledAsync(packageName, userHandle, enabled);
+            transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
         }
         if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
-            setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
+            transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
         }
     }
 
-    private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) {
-        mExecutor.execute(() -> {
-            if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled));
-            try {
-                if (enabled) {
-                    mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle);
-                } else {
-                    mOverlayManager.setEnabled(pkg, false, userHandle);
-                }
-            } catch (SecurityException | IllegalStateException e) {
-                Log.e(TAG,
-                        String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
-            }
-        });
-    }
-
     /**
      * @inherit
      */
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index e9fcf1a..365cd2a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -20,10 +20,21 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
 import android.widget.ToastPresenter;
 
 import com.android.internal.R;
+import com.android.launcher3.icons.IconFactory;
 import com.android.systemui.plugins.ToastPlugin;
 
 /**
@@ -35,23 +46,43 @@
     final CharSequence mText;
     final ToastPlugin.Toast mPluginToast;
 
-    final int mDefaultGravity;
-    final int mDefaultY;
+    private final String mPackageName;
+    private final int mUserId;
+    private final LayoutInflater mLayoutInflater;
+    private final boolean mToastStyleEnabled;
+
     final int mDefaultX = 0;
     final int mDefaultHorizontalMargin = 0;
     final int mDefaultVerticalMargin = 0;
 
-    SystemUIToast(Context context, CharSequence text) {
-        this(context, text, null);
+    private int mDefaultY;
+    private int mDefaultGravity;
+
+    @NonNull private final View mToastView;
+    @Nullable private final Animator mInAnimator;
+    @Nullable private final Animator mOutAnimator;
+
+    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+            String packageName, int userId, boolean toastStyleEnabled, int orientation) {
+        this(layoutInflater, context, text, null, packageName, userId,
+                toastStyleEnabled, orientation);
     }
 
-    SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+            ToastPlugin.Toast pluginToast, String packageName, int userId,
+            boolean toastStyleEnabled, int orientation) {
+        mToastStyleEnabled = toastStyleEnabled;
+        mLayoutInflater = layoutInflater;
         mContext = context;
         mText = text;
         mPluginToast = pluginToast;
+        mPackageName = packageName;
+        mUserId = userId;
+        mToastView = inflateToastView();
+        mInAnimator = createInAnimator();
+        mOutAnimator = createOutAnimator();
 
-        mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
-        mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+        onOrientationChange(orientation);
     }
 
     @Override
@@ -102,28 +133,19 @@
     @Override
     @NonNull
     public View getView() {
-        if (isPluginToast() && mPluginToast.getView() != null) {
-            return mPluginToast.getView();
-        }
-        return ToastPresenter.getTextToastView(mContext, mText);
+        return mToastView;
     }
 
     @Override
     @Nullable
     public Animator getInAnimation() {
-        if (isPluginToast() && mPluginToast.getInAnimation() != null) {
-            return mPluginToast.getInAnimation();
-        }
-        return null;
+        return mInAnimator;
     }
 
     @Override
     @Nullable
     public Animator getOutAnimation() {
-        if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
-            return mPluginToast.getOutAnimation();
-        }
-        return null;
+        return mOutAnimator;
     }
 
     /**
@@ -136,4 +158,80 @@
     private boolean isPluginToast() {
         return mPluginToast != null;
     }
+
+    private View inflateToastView() {
+        if (isPluginToast() && mPluginToast.getView() != null) {
+            return mPluginToast.getView();
+        }
+
+        View toastView;
+        if (mToastStyleEnabled) {
+            toastView = mLayoutInflater.inflate(
+                    com.android.systemui.R.layout.text_toast, null);
+            ((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText);
+
+            ((ImageView) toastView.findViewById(com.android.systemui.R.id.icon))
+                    .setImageDrawable(getBadgedIcon(mContext, mPackageName, mUserId));
+        } else {
+            toastView = ToastPresenter.getTextToastView(mContext, mText);
+        }
+
+        return toastView;
+    }
+
+    /**
+     * Called on orientation changes to update parameters associated with the toast placement.
+     */
+    public void onOrientationChange(int orientation) {
+        if (mPluginToast != null) {
+            mPluginToast.onOrientationChange(orientation);
+        }
+
+        mDefaultY = mContext.getResources().getDimensionPixelSize(
+                mToastStyleEnabled
+                        ? com.android.systemui.R.dimen.toast_y_offset
+                        : R.dimen.toast_y_offset);
+        mDefaultGravity =
+                mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+    }
+
+    private Animator createInAnimator() {
+        if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+            return mPluginToast.getInAnimation();
+        }
+
+        return mToastStyleEnabled
+                ? ToastDefaultAnimation.Companion.toastIn(getView())
+                : null;
+    }
+
+    private Animator createOutAnimator() {
+        if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+            return mPluginToast.getOutAnimation();
+        }
+        return mToastStyleEnabled
+                ? ToastDefaultAnimation.Companion.toastOut(getView())
+                : null;
+    }
+
+    /**
+     * Get badged app icon if necessary, similar as used in the Settings UI.
+     * @return The icon to use
+     */
+    public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
+            int userId) {
+        final PackageManager packageManager = context.getPackageManager();
+        try {
+            final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
+            UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
+            IconFactory iconFactory = IconFactory.obtain(context);
+            Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
+                    appInfo.loadUnbadgedIcon(packageManager), user, false).icon;
+            return new BitmapDrawable(context.getResources(), iconBmp);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e("SystemUIToast", "could not load icon for package=" + packageName + " e=" + e);
+            return null;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
new file mode 100644
index 0000000..603d690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 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.toast
+
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.animation.AnimatorSet
+
+class ToastDefaultAnimation {
+    /**
+     * sum of the in and out animation durations cannot exceed
+     * [com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER] to prevent the toast
+     * window from being removed before animations are completed
+     */
+    companion object {
+        // total duration shouldn't exceed NotificationManagerService's delay for "in" animation
+        fun toastIn(view: View): AnimatorSet? {
+            val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+            val text: View? = view.findViewById(com.android.systemui.R.id.text)
+            if (icon == null || text == null) {
+                return null
+            }
+            val linearInterp = LinearInterpolator()
+            val scaleInterp = PathInterpolator(0f, 0f, 0f, 1f)
+            val sX = ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1f).apply {
+                interpolator = scaleInterp
+                duration = 333
+            }
+            val sY = ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1f).apply {
+                interpolator = scaleInterp
+                duration = 333
+            }
+            val vA = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 66
+            }
+            text.alpha = 0f // Set now otherwise won't apply until start delay
+            val tA = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 283
+                startDelay = 50
+            }
+            icon.alpha = 0f // Set now otherwise won't apply until start delay
+            val iA = ObjectAnimator.ofFloat(icon, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 283
+                startDelay = 50
+            }
+            return AnimatorSet().apply {
+                playTogether(sX, sY, vA, tA, iA)
+            }
+        }
+
+        fun toastOut(view: View): AnimatorSet? {
+            // total duration shouldn't exceed NotificationManagerService's delay for "out" anim
+            val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+            val text: View? = view.findViewById(com.android.systemui.R.id.text)
+            if (icon == null || text == null) {
+                return null
+            }
+            val linearInterp = LinearInterpolator()
+            val scaleInterp = PathInterpolator(0.3f, 0f, 1f, 1f)
+            val sX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.9f).apply {
+                interpolator = scaleInterp
+                duration = 250
+            }
+            val sY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.9f).apply {
+                interpolator = scaleInterp
+                duration = 250
+            }
+            val vA = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 100
+                startDelay = 150
+            }
+            val tA = ObjectAnimator.ofFloat(text, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 166
+            }
+            val iA = ObjectAnimator.ofFloat(icon, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 166
+            }
+            return AnimatorSet().apply {
+                playTogether(sX, sY, vA, tA, iA)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index d8cb61c..8b782d4 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -17,6 +17,7 @@
 package com.android.systemui.toast;
 
 import android.content.Context;
+import android.view.LayoutInflater;
 
 import androidx.annotation.NonNull;
 
@@ -26,6 +27,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.ToastPlugin;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -40,10 +42,18 @@
 public class ToastFactory implements Dumpable {
     // only one ToastPlugin can be connected at a time.
     private ToastPlugin mPlugin;
+    private final LayoutInflater mLayoutInflater;
+    private final boolean mToastStyleEnabled;
 
     @Inject
-    public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+    public ToastFactory(
+            LayoutInflater layoutInflater,
+            PluginManager pluginManager,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
+        mLayoutInflater = layoutInflater;
         dumpManager.registerDumpable("ToastFactory", this);
+        mToastStyleEnabled = featureFlags.isToastStyleEnabled();
         pluginManager.addPluginListener(
                 new PluginListener<ToastPlugin>() {
                     @Override
@@ -64,11 +74,13 @@
      * Create a toast to be shown by ToastUI.
      */
     public SystemUIToast createToast(Context context, CharSequence text, String packageName,
-            int userId) {
+            int userId, int orientation) {
         if (isPluginAvailable()) {
-            return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+            return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+                    packageName, userId), packageName, userId, mToastStyleEnabled, orientation);
         }
-        return new SystemUIToast(context, text);
+        return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+                mToastStyleEnabled, orientation);
     }
 
     private boolean isPluginAvailable() {
@@ -79,5 +91,6 @@
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("ToastFactory:");
         pw.println("    mAttachedPlugin=" + mPlugin);
+        pw.println("    mToastStyleEnabled=" + mToastStyleEnabled);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 78173cf..51541bd 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -49,6 +49,15 @@
         })
     }
 
+    fun logOrientationChange(text: String, isPortrait: Boolean) {
+        log(DEBUG, {
+            str1 = text
+            bool1 = isPortrait
+        }, {
+            "Orientation change for toast. msg=\'$str1\' isPortrait=$bool1"
+        })
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 409d136..92ea1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,27 +16,28 @@
 
 package com.android.systemui.toast;
 
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
 import android.animation.Animator;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.ITransientNotificationCallback;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
-import android.widget.Toast;
 import android.widget.ToastPresenter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Objects;
 
@@ -58,18 +59,19 @@
     private final IAccessibilityManager mIAccessibilityManager;
     private final AccessibilityManager mAccessibilityManager;
     private final ToastFactory mToastFactory;
-    private final DelayableExecutor mMainExecutor;
     private final ToastLogger mToastLogger;
     private SystemUIToast mToast;
     @Nullable private ToastPresenter mPresenter;
     @Nullable private ITransientNotificationCallback mCallback;
+    private ToastOutAnimatorListener mToastOutAnimatorListener;
+
+    private int mOrientation = ORIENTATION_PORTRAIT;
 
     @Inject
     public ToastUI(
             Context context,
             CommandQueue commandQueue,
             ToastFactory toastFactory,
-            @Main DelayableExecutor mainExecutor,
             ToastLogger toastLogger) {
         this(context, commandQueue,
                 INotificationManager.Stub.asInterface(
@@ -77,21 +79,19 @@
                 IAccessibilityManager.Stub.asInterface(
                         ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
                 toastFactory,
-                mainExecutor,
                 toastLogger);
     }
 
     @VisibleForTesting
     ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
             @Nullable IAccessibilityManager accessibilityManager,
-            ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+            ToastFactory toastFactory, ToastLogger toastLogger
     ) {
         super(context);
         mCommandQueue = commandQueue;
         mNotificationManager = notificationManager;
         mIAccessibilityManager = accessibilityManager;
         mToastFactory = toastFactory;
-        mMainExecutor = mainExecutor;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mToastLogger = toastLogger;
     }
@@ -105,36 +105,38 @@
     @MainThread
     public void showToast(int uid, String packageName, IBinder token, CharSequence text,
             IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
-        if (mPresenter != null) {
-            hideCurrentToast();
-        }
-        UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
-        Context context = mContext.createContextAsUser(userHandle, 0);
-        mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+        Runnable showToastRunnable = () -> {
+            UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+            Context context = mContext.createContextAsUser(userHandle, 0);
+            mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+                    userHandle.getIdentifier(), mOrientation);
 
-        if (mToast.hasCustomAnimation()) {
             if (mToast.getInAnimation() != null) {
                 mToast.getInAnimation().start();
             }
-            final Animator hideAnimator = mToast.getOutAnimation();
-            if (hideAnimator != null) {
-                final long durationMillis = duration == Toast.LENGTH_LONG
-                        ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
-                final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
-                        (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
-                mMainExecutor.executeDelayed(() -> hideAnimator.start(),
-                        updatedDuration - hideAnimator.getTotalDuration());
-            }
+
+            mCallback = callback;
+            mPresenter = new ToastPresenter(context, mIAccessibilityManager,
+                    mNotificationManager, packageName);
+            // Set as trusted overlay so touches can pass through toasts
+            mPresenter.getLayoutParams().setTrustedOverlay();
+            mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+            mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+                    mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+                    mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
+        };
+
+        if (mToastOutAnimatorListener != null) {
+            // if we're currently animating out a toast, show new toast after prev toast is hidden
+            mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable);
+        } else if (mPresenter != null) {
+            // if there's a toast already showing that we haven't tried hiding yet, hide it and
+            // then show the next toast after its hidden animation is done
+            hideCurrentToast(showToastRunnable);
+        } else {
+            // else, show this next toast immediately
+            showToastRunnable.run();
         }
-        mCallback = callback;
-        mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
-                packageName);
-        // Set as trusted overlay so touches can pass through toasts
-        mPresenter.getLayoutParams().setTrustedOverlay();
-        mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
-        mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
-                mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
-                mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
     }
 
     @Override
@@ -146,12 +148,88 @@
             return;
         }
         mToastLogger.logOnHideToast(packageName, token.toString());
-        hideCurrentToast();
+        hideCurrentToast(null);
     }
 
     @MainThread
-    private void hideCurrentToast() {
-        mPresenter.hide(mCallback);
+    private void hideCurrentToast(Runnable runnable) {
+        if (mToast.getOutAnimation() != null) {
+            Animator animator = mToast.getOutAnimation();
+            mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
+                    runnable);
+            animator.addListener(mToastOutAnimatorListener);
+            animator.start();
+        } else {
+            mPresenter.hide(mCallback);
+            if (runnable != null) {
+                runnable.run();
+            }
+        }
+        mToast = null;
         mPresenter = null;
+        mCallback = null;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (newConfig.orientation != mOrientation) {
+            mOrientation = newConfig.orientation;
+            if (mToast != null) {
+                mToastLogger.logOrientationChange(mToast.mText.toString(),
+                        mOrientation == ORIENTATION_PORTRAIT);
+                mToast.onOrientationChange(mOrientation);
+                mPresenter.updateLayoutParams(
+                        mToast.getXOffset(),
+                        mToast.getYOffset(),
+                        mToast.getHorizontalMargin(),
+                        mToast.getVerticalMargin(),
+                        mToast.getGravity());
+            }
+        }
+    }
+
+    /**
+     * Once the out animation for a toast is finished, start showing the next toast.
+     */
+    class ToastOutAnimatorListener implements Animator.AnimatorListener {
+        final ToastPresenter mPrevPresenter;
+        final ITransientNotificationCallback mPrevCallback;
+        @Nullable Runnable mShowNextToastRunnable;
+
+        ToastOutAnimatorListener(
+                @NonNull ToastPresenter presenter,
+                @NonNull ITransientNotificationCallback callback,
+                @Nullable Runnable runnable) {
+            mPrevPresenter = presenter;
+            mPrevCallback = callback;
+            mShowNextToastRunnable = runnable;
+        }
+
+        void setShowNextToastRunnable(Runnable runnable) {
+            mShowNextToastRunnable = runnable;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mPrevPresenter.hide(mPrevCallback);
+            if (mShowNextToastRunnable != null) {
+                mShowNextToastRunnable.run();
+            }
+            mToastOutAnimatorListener = null;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            onAnimationEnd(animation);
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 0795d89..ff28819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -28,6 +29,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -49,7 +51,7 @@
 import dagger.Provides;
 
 /**
- * Dagger module for TV Pip.
+ * Provides TV specific dependencies for Pip.
  */
 @Module(includes = {WMShellBaseModule.class})
 public abstract class TvPipModule {
@@ -143,7 +145,8 @@
             PipAnimationController pipAnimationController,
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index f23367b..141b9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -39,11 +40,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for the TV SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = {TvPipModule.class})
 public class TvWMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -53,16 +63,20 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideSplitScreen(Context context,
+    static LegacySplitScreenController provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 81ac21c..ec61db5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
@@ -73,7 +74,20 @@
 import javax.inject.Inject;
 
 /**
- * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ * A SystemUI service that starts with the SystemUI application and sets up any bindings between
+ * Shell and SysUI components.  This service starts happens after the {@link WMComponent} has
+ * already been initialized and may only reference Shell components that are explicitly exported to
+ * SystemUI (see {@link WMComponent}.
+ *
+ * eg. SysUI application starts
+ *     -> SystemUIFactory is initialized
+ *       -> WMComponent is created
+ *         -> WMShellBaseModule dependencies are injected
+ *         -> WMShellModule (form-factory specific) dependencies are injected
+ *       -> SysUIComponent is created
+ *         -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
+ *     -> SysUI services are started
+ *       -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
  */
 @SysUISingleton
 public final class WMShell extends SystemUI
@@ -142,6 +156,8 @@
 
     @Override
     public void start() {
+        // TODO: Consider piping config change and other common calls to a shell component to
+        //  delegate internally
         mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b42dde6..449db61 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -32,6 +32,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.FullscreenTaskListener;
@@ -45,6 +46,7 @@
 import com.android.wm.shell.TaskViewFactoryController;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DisplayController;
@@ -63,6 +65,7 @@
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -70,8 +73,8 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -85,8 +88,13 @@
 import dagger.Provides;
 
 /**
- * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
- * should be shared among different branches of SystemUI.
+ * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines *common* dependencies across various SystemUI implementations,
+ * dependencies that are device/form factor SystemUI implementation specific should go into their
+ * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
 @Module
 public abstract class WMShellBaseModule {
@@ -174,53 +182,9 @@
         }
     }
 
-    @WMSingleton
-    @Provides
-    static ShellInit provideShellInit(DisplayImeController displayImeController,
-            DragAndDropController dragAndDropController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
-            FullscreenTaskListener fullscreenTaskListener,
-            Transitions transitions,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return ShellInitImpl.create(displayImeController,
-                dragAndDropController,
-                shellTaskOrganizer,
-                legacySplitScreenOptional,
-                splitScreenOptional,
-                appPairsOptional,
-                fullscreenTaskListener,
-                transitions,
-                mainExecutor);
-    }
-
-    /**
-     * Note, this is only optional because we currently pass this to the SysUI component scope and
-     * for non-primary users, we may inject a null-optional for that dependency.
-     */
-    @WMSingleton
-    @Provides
-    static Optional<ShellCommandHandler> provideShellCommandHandler(
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
-                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
-                hideDisplayCutout, appPairsOptional, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
 
     @WMSingleton
     @Provides
@@ -238,8 +202,45 @@
 
     @WMSingleton
     @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
+    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
+            Context context, SizeCompatUIController sizeCompatUI) {
+        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
+    }
+
+    @WMSingleton
+    @Provides
+    static SizeCompatUIController provideSizeCompatUIController(Context context,
+            DisplayController displayController, DisplayImeController imeController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SyncTransactionQueue(pool, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SystemWindows provideSystemWindows(DisplayController displayController,
+            IWindowManager wmService) {
+        return new SystemWindows(displayController, wmService);
+    }
+
+    // We currently dedupe multiple messages, so we use the shell main handler directly
+    @WMSingleton
+    @Provides
+    static TaskStackListenerImpl providerTaskStackListenerImpl(
+            @ShellMainThread Handler mainHandler) {
+        return new TaskStackListenerImpl(mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static TransactionPool provideTransactionPool() {
+        return new TransactionPool();
     }
 
     @WMSingleton
@@ -249,10 +250,99 @@
         return new WindowManagerShellWrapper(mainExecutor);
     }
 
+    //
+    // Bubbles
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) {
+        return bubbleController.map((controller) -> controller.asBubbles());
+    }
+
+    // Note: Handler needed for LauncherApps.register
+    @WMSingleton
+    @Provides
+    static Optional<BubbleController> provideBubbleController(Context context,
+            FloatingContentCoordinator floatingContentCoordinator,
+            IStatusBarService statusBarService,
+            WindowManager windowManager,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            ShellTaskOrganizer organizer,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.of(BubbleController.create(context, null /* synchronizer */,
+                floatingContentCoordinator, statusBarService, windowManager,
+                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+                mainExecutor, mainHandler));
+    }
+
+    //
+    // Fullscreen
+    //
+
+    @WMSingleton
+    @Provides
+    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
+        return new FullscreenTaskListener(syncQueue);
+    }
+
+    //
+    // Hide display cutout
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutout> provideHideDisplayCutout(
+            Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
+        return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
+    }
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
+            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.ofNullable(
+                HideDisplayCutoutController.create(context, displayController, mainExecutor));
+    }
+
+    //
+    // One handed mode (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) {
+        return oneHandedController.map((controller) -> controller.asOneHanded());
+    }
+
+    // Needs the shell main handler for ContentObserver callbacks
+    @WMSingleton
+    @Provides
+    static Optional<OneHandedController> provideOneHandedController(Context context,
+            DisplayController displayController, TaskStackListenerImpl taskStackListener,
+            UiEventLogger uiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.ofNullable(OneHandedController.create(context, displayController,
+                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    }
+
+    //
+    // Pip (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
     @WMSingleton
     @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
-            IActivityManager activityManager,
             PipTouchHandler pipTouchHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
@@ -268,37 +358,38 @@
 
     @WMSingleton
     @Provides
-    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
-            PackageManager packageManager) {
-        return new PipUiEventLogger(uiEventLogger, packageManager);
-    }
-
-    @WMSingleton
-    @Provides
     static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
         return new PipSurfaceTransactionHelper(context);
     }
 
     @WMSingleton
     @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+            PackageManager packageManager) {
+        return new PipUiEventLogger(uiEventLogger, packageManager);
+    }
+
+    //
+    // Shell transitions
+    //
+
+    @WMSingleton
+    @Provides
+    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+        return Transitions.asRemoteTransitions(transitions);
     }
 
     @WMSingleton
     @Provides
-    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new SyncTransactionQueue(pool, mainExecutor);
+    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor) {
+        return new Transitions(organizer, pool, mainExecutor, animExecutor);
     }
 
-    @WMSingleton
-    @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context, SizeCompatUI sizeCompatUI) {
-        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
-    }
+    //
+    // Split/multiwindow
+    //
 
     @WMSingleton
     @Provides
@@ -307,17 +398,6 @@
         return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
     }
 
-    // We currently dedupe multiple messages, so we use the shell main handler directly
-    @WMSingleton
-    @Provides
-    static TaskStackListenerImpl providerTaskStackListenerImpl(
-            @ShellMainThread Handler mainHandler) {
-        return new TaskStackListenerImpl(mainHandler);
-    }
-
-    @BindsOptionalOf
-    abstract LegacySplitScreen optionalLegacySplitScreen();
-
     @WMSingleton
     @Provides
     static Optional<SplitScreen> provideSplitScreen(
@@ -340,81 +420,91 @@
         }
     }
 
+    // Legacy split (optional feature)
+
+    @WMSingleton
+    @Provides
+    static Optional<LegacySplitScreen> provideLegacySplitScreen(
+            Optional<LegacySplitScreenController> splitScreenController) {
+        return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
+    }
+
     @BindsOptionalOf
-    abstract AppPairs optionalAppPairs();
+    abstract LegacySplitScreenController optionalLegacySplitScreenController();
 
-    // Note: Handler needed for LauncherApps.register
+    // App Pairs (optional feature)
+
     @WMSingleton
     @Provides
-    static Optional<Bubbles> provideBubbles(Context context,
-            FloatingContentCoordinator floatingContentCoordinator,
-            IStatusBarService statusBarService,
-            WindowManager windowManager,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps,
-            UiEventLogger uiEventLogger,
-            ShellTaskOrganizer organizer,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.of(BubbleController.create(context, null /* synchronizer */,
-                floatingContentCoordinator, statusBarService, windowManager,
-                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
-                mainExecutor, mainHandler));
+    static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
+        return appPairsController.map((controller) -> controller.asAppPairs());
     }
 
-    // Needs the shell main handler for ContentObserver callbacks
+    @BindsOptionalOf
+    abstract AppPairsController optionalAppPairs();
+
+    //
+    // Task view factory
+    //
+
     @WMSingleton
     @Provides
-    static Optional<OneHanded> provideOneHandedController(Context context,
-            DisplayController displayController, TaskStackListenerImpl taskStackListener,
-            UiEventLogger uiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.ofNullable(OneHandedController.create(context, displayController,
-                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    static Optional<TaskViewFactory> provideTaskViewFactory(
+            TaskViewFactoryController taskViewFactoryController) {
+        return Optional.of(taskViewFactoryController.asTaskViewFactory());
     }
 
     @WMSingleton
     @Provides
-    static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
-            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(
-                HideDisplayCutoutController.create(context, displayController, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer,
+    static TaskViewFactoryController provideTaskViewFactoryController(
+            ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor)
-                .getTaskViewFactory());
+        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
     }
 
+    //
+    // Misc
+    //
+
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        return new FullscreenTaskListener(syncQueue);
-    }
-
-    @WMSingleton
-    @Provides
-    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
-        return Transitions.asRemoteTransitions(transitions);
-    }
-
-    @WMSingleton
-    @Provides
-    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(organizer, pool, mainExecutor, animExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
-            DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
-        return SizeCompatUIController.create(context, displayController, imeController,
+    static ShellInit provideShellInit(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<AppPairsController> appPairsOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            Transitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return ShellInitImpl.create(displayImeController,
+                dragAndDropController,
+                shellTaskOrganizer,
+                legacySplitScreenOptional,
+                splitScreenOptional,
+                appPairsOptional,
+                fullscreenTaskListener,
+                transitions,
                 mainExecutor);
     }
+
+    /**
+     * Note, this is only optional because we currently pass this to the SysUI component scope and
+     * for non-primary users, we may inject a null-optional for that dependency.
+     */
+    @WMSingleton
+    @Provides
+    static Optional<ShellCommandHandler> provideShellCommandHandler(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
+                hideDisplayCutout, appPairsOptional, mainExecutor));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 2aaa095..997b488 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -21,10 +21,10 @@
 import android.os.Handler;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
@@ -36,7 +36,6 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -60,11 +59,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = WMShellBaseModule.class)
 public class WMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -74,29 +82,37 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideLegacySplitScreen(Context context,
+    static LegacySplitScreenController provideLegacySplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
 
     @WMSingleton
     @Provides
-    static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
+    static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController,
+        return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
                 mainExecutor);
     }
 
+    //
+    // Pip
+    //
+
     @WMSingleton
     @Provides
     static Optional<Pip> providePip(Context context, DisplayController displayController,
@@ -161,7 +177,8 @@
             PipAnimationController pipAnimationController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a2eaea1..70a7b7a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -34,6 +34,7 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -75,6 +76,8 @@
     NotificationIconAreaController mNotificationIconAreaController;
     @Mock
     ContentResolver mContentResolver;
+    @Mock
+    BroadcastDispatcher mBroadcastDispatcher;
 
     private KeyguardClockSwitchController mController;
 
@@ -94,7 +97,8 @@
                 mClockManager,
                 mKeyguardSliceViewController,
                 mNotificationIconAreaController,
-                mContentResolver);
+                mContentResolver,
+                mBroadcastDispatcher);
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
index 53d84db..7b4f14d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
@@ -34,9 +34,9 @@
 
 import kotlin.math.ceil
 
-private val PAINT = arrayListOf(TextPaint().apply {
+private val PAINT = TextPaint().apply {
     textSize = 32f
-})
+}
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -49,10 +49,10 @@
 
     @Test
     fun testAnimationStarted() {
-        val layout = makeLayout("Hello, World", PAINT[0])
+        val layout = makeLayout("Hello, World", PAINT)
         val valueAnimator = mock(ValueAnimator::class.java)
         val textInterpolator = mock(TextInterpolator::class.java)
-        val paint = arrayListOf(mock(TextPaint::class.java))
+        val paint = mock(TextPaint::class.java)
         `when`(textInterpolator.targetPaint).thenReturn(paint)
 
         val textAnimator = TextAnimator(layout, {}).apply {
@@ -81,10 +81,10 @@
 
     @Test
     fun testAnimationNotStarted() {
-        val layout = makeLayout("Hello, World", PAINT[0])
+        val layout = makeLayout("Hello, World", PAINT)
         val valueAnimator = mock(ValueAnimator::class.java)
         val textInterpolator = mock(TextInterpolator::class.java)
-        val paint = arrayListOf(mock(TextPaint::class.java))
+        val paint = mock(TextPaint::class.java)
         `when`(textInterpolator.targetPaint).thenReturn(paint)
 
         val textAnimator = TextAnimator(layout, {}).apply {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
index 1206dab..149e179 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
@@ -21,9 +21,9 @@
 import android.testing.AndroidTestingRunner
 import android.text.Layout
 import android.text.StaticLayout
-import android.text.TextPaint
 import android.text.TextDirectionHeuristic
 import android.text.TextDirectionHeuristics
+import android.text.TextPaint
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
@@ -40,13 +40,13 @@
     textSize = 32f
 }
 
-private val START_PAINT = arrayListOf(TextPaint(PAINT).apply {
+private val START_PAINT = TextPaint(PAINT).apply {
     fontVariationSettings = "'wght' 400"
-})
+}
 
-private val END_PAINT = arrayListOf(TextPaint(PAINT).apply {
+private val END_PAINT = TextPaint(PAINT).apply {
     fontVariationSettings = "'wght' 700"
-})
+}
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -67,16 +67,16 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // Just after created TextInterpolator, it should have 0 progress.
         assertThat(interp.progress).isEqualTo(0f)
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(TEXT, START_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
     }
@@ -86,15 +86,15 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         interp.progress = 1f
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(TEXT, END_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
     }
@@ -104,10 +104,10 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // We cannot expect exact text layout of the middle position since we don't use text shaping
@@ -115,9 +115,9 @@
         // end state.
         interp.progress = 0.5f
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0])
+        assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT)
             .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
-        assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0])
+        assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT)
             .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
     }
 
@@ -126,10 +126,10 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         interp.progress = 0.5f
@@ -148,16 +148,16 @@
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // Just after created TextInterpolator, it should have 0 progress.
         assertThat(interp.progress).isEqualTo(0f)
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.LTR)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.LTR)
                 .toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
@@ -168,16 +168,16 @@
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // Just after created TextInterpolator, it should have 0 progress.
         assertThat(interp.progress).isEqualTo(0f)
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.RTL)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
                 .toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 6db21f9..4ee2759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.people;
 
 import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -52,6 +53,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
@@ -64,6 +66,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -82,10 +87,17 @@
 
     private static final int WIDGET_ID_WITH_SHORTCUT = 1;
     private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2;
-    private static final String SHORTCUT_ID = "101";
+    private static final String SHORTCUT_ID_1 = "101";
+    private static final String SHORTCUT_ID_2 = "202";
+    private static final String SHORTCUT_ID_3 = "303";
+    private static final String SHORTCUT_ID_4 = "404";
     private static final String NOTIFICATION_KEY = "notification_key";
     private static final String NOTIFICATION_CONTENT = "notification_content";
     private static final String TEST_LOOKUP_KEY = "lookup_key";
+    private static final String NOTIFICATION_TEXT_1 = "notification_text_1";
+    private static final String NOTIFICATION_TEXT_2 = "notification_text_2";
+    private static final String NOTIFICATION_TEXT_3 = "notification_text_3";
+    private static final String NOTIFICATION_TEXT_4 = "notification_text_4";
     private static final int TEST_COLUMN_INDEX = 1;
     private static final Uri URI = Uri.parse("fake_uri");
     private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
@@ -97,20 +109,66 @@
             .build();
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
-                    .Builder(SHORTCUT_ID, "username", ICON, new Intent())
+                    .Builder(SHORTCUT_ID_1, "username", ICON, new Intent())
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
                     .build();
 
     private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
-            SHORTCUT_ID).setLongLabel(
+            SHORTCUT_ID_1).setLongLabel(
             "name").setPerson(PERSON)
             .build();
     private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext,
-            SHORTCUT_ID).setLongLabel(
+            SHORTCUT_ID_1).setLongLabel(
             "name")
             .build();
+    private final Notification mNotification1 = new Notification.Builder(mContext, "test")
+            .setContentTitle("TEST_TITLE")
+            .setContentText("TEST_TEXT")
+            .setShortcutId(SHORTCUT_ID_1)
+            .setStyle(new Notification.MessagingStyle(PERSON)
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_1, 0, PERSON))
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_2, 20, PERSON))
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_3, 10, PERSON))
+            )
+            .build();
+    private final Notification mNotification2 = new Notification.Builder(mContext, "test2")
+            .setContentTitle("TEST_TITLE")
+            .setContentText("OTHER_TEXT")
+            .setShortcutId(SHORTCUT_ID_2)
+            .setStyle(new Notification.MessagingStyle(PERSON)
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_4, 0, PERSON))
+            )
+            .build();
+    private final Notification mNotification3 = new Notification.Builder(mContext, "test2")
+            .setContentTitle("TEST_TITLE")
+            .setContentText("OTHER_TEXT")
+            .setShortcutId(SHORTCUT_ID_3)
+            .setStyle(new Notification.MessagingStyle(PERSON))
+            .build();
+    private final NotificationEntry mNotificationEntry1 = new NotificationEntryBuilder()
+            .setNotification(mNotification1)
+            .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_1).build())
+            .setUser(UserHandle.of(0))
+            .setPkg(PACKAGE_NAME)
+            .build();
+    private final NotificationEntry mNotificationEntry2 = new NotificationEntryBuilder()
+            .setNotification(mNotification2)
+            .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_2).build())
+            .setUser(UserHandle.of(0))
+            .setPkg(PACKAGE_NAME)
+            .build();
+    private final NotificationEntry mNotificationEntry3 = new NotificationEntryBuilder()
+            .setNotification(mNotification3)
+            .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_3).build())
+            .setUser(UserHandle.of(0))
+            .setPkg(PACKAGE_NAME)
+            .build();
 
     @Mock
     private NotificationListener mListenerService;
@@ -130,6 +188,8 @@
     private ContentResolver mMockContentResolver;
     @Mock
     private Context mMockContext;
+    @Mock
+    private NotificationEntryManager mNotificationEntryManager;
 
     @Before
     public void setUp() throws RemoteException {
@@ -152,15 +212,17 @@
                 isNull())).thenReturn(mMockCursor);
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
+        when(mNotificationEntryManager.getVisibleNotifications())
+                .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
     }
 
     @Test
     public void testGetTilesReturnsSortedListWithMultipleRecentConversations() throws Exception {
         // Ensure the less-recent Important conversation is before more recent conversations.
         ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID, false, 3);
+                SHORTCUT_ID_1, false, 3);
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID + 1,
+                SHORTCUT_ID_1 + 1,
                 true, 1);
         when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
@@ -169,9 +231,9 @@
         // Ensure the non-Important conversation is sorted between these recent conversations.
         ConversationChannel recentConversationBeforeNonImportantConversation =
                 getConversationChannel(
-                        SHORTCUT_ID + 2, 4);
+                        SHORTCUT_ID_1 + 2, 4);
         ConversationChannel recentConversationAfterNonImportantConversation =
-                getConversationChannel(SHORTCUT_ID + 3,
+                getConversationChannel(SHORTCUT_ID_1 + 3,
                         2);
         when(mPeopleManager.getRecentConversations()).thenReturn(
                 new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
@@ -179,7 +241,8 @@
 
         List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles(
                 mContext, mNotificationManager, mPeopleManager,
-                mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList());
+                mLauncherApps, mNotificationEntryManager)
+                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
 
         assertThat(orderedShortcutIds).containsExactly(
                 // Even though the oldest conversation, should be first since "important"
@@ -196,11 +259,11 @@
             throws Exception {
         // Ensure the less-recent Important conversation is before more recent conversations.
         ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID, false, 3);
+                SHORTCUT_ID_1, false, 3);
         ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID + 1, true, 3);
+                SHORTCUT_ID_1 + 1, true, 3);
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID + 2,
+                SHORTCUT_ID_1 + 2,
                 true, 1);
         when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
@@ -210,9 +273,9 @@
         // Ensure the non-Important conversation is sorted between these recent conversations.
         ConversationChannel recentConversationBeforeNonImportantConversation =
                 getConversationChannel(
-                        SHORTCUT_ID + 3, 4);
+                        SHORTCUT_ID_1 + 3, 4);
         ConversationChannel recentConversationAfterNonImportantConversation =
-                getConversationChannel(SHORTCUT_ID + 4,
+                getConversationChannel(SHORTCUT_ID_1 + 4,
                         2);
         when(mPeopleManager.getRecentConversations()).thenReturn(
                 new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
@@ -220,7 +283,8 @@
 
         List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles(
                 mContext, mNotificationManager, mPeopleManager,
-                mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList());
+                mLauncherApps, mNotificationEntryManager)
+                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
 
         assertThat(orderedShortcutIds).containsExactly(
                 // Important conversations should be sorted at the beginning.
@@ -238,7 +302,7 @@
         Notification notification = new Notification.Builder(mContext, "test")
                 .setContentTitle("TEST_TITLE")
                 .setContentText("TEST_TEXT")
-                .setShortcutId(SHORTCUT_ID)
+                .setShortcutId(SHORTCUT_ID_1)
                 .build();
         StatusBarNotification sbn = new SbnBuilder()
                 .setNotification(notification)
@@ -341,24 +405,126 @@
 
     @Test
     public void testGetLastMessagingStyleMessage() {
-        Notification notification = new Notification.Builder(mContext, "test")
-                .setContentTitle("TEST_TITLE")
-                .setContentText("TEST_TEXT")
-                .setShortcutId(SHORTCUT_ID)
-                .setStyle(new Notification.MessagingStyle(PERSON)
-                        .addMessage(new Notification.MessagingStyle.Message("text1", 0, PERSON))
-                        .addMessage(new Notification.MessagingStyle.Message("text2", 20, PERSON))
-                        .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON))
-                )
-                .build();
         StatusBarNotification sbn = new SbnBuilder()
-                .setNotification(notification)
+                .setNotification(mNotification1)
                 .build();
 
         Notification.MessagingStyle.Message lastMessage =
                 PeopleSpaceUtils.getLastMessagingStyleMessage(sbn);
 
-        assertThat(lastMessage.getText()).isEqualTo("text2");
+        assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+    }
+
+    @Test
+    public void testAugmentTileFromNotification() {
+        StatusBarNotification sbn = new SbnBuilder()
+                .setNotification(mNotification1)
+                .build();
+
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromNotification(tile, sbn);
+
+        assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+    }
+
+    @Test
+    public void testAugmentTileFromNotificationNoContent() {
+        StatusBarNotification sbn = new SbnBuilder()
+                .setNotification(mNotification3)
+                .build();
+
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_3, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromNotification(tile, sbn);
+
+        assertThat(actual.getNotificationKey()).isEqualTo(null);
+        assertThat(actual.getNotificationContent()).isEqualTo(null);
+    }
+
+    @Test
+    public void testAugmentTileFromVisibleNotifications() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromVisibleNotifications(tile,
+                        Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1));
+
+        assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+    }
+
+    @Test
+    public void testAugmentTileFromVisibleNotificationsDifferentShortcutId() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_4, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromVisibleNotifications(tile,
+                        Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1));
+
+        assertThat(actual.getNotificationContent()).isEqualTo(null);
+    }
+
+    @Test
+    public void testAugmentTilesFromVisibleNotificationsSingleTile() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        List<PeopleSpaceTile> actualList = PeopleSpaceUtils
+                .augmentTilesFromVisibleNotifications(List.of(tile), mNotificationEntryManager);
+
+        assertThat(actualList.size()).isEqualTo(1);
+        assertThat(actualList.get(0).getNotificationContent().toString())
+                .isEqualTo(NOTIFICATION_TEXT_2);
+
+        verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
+    }
+
+    @Test
+    public void testAugmentTilesFromVisibleNotificationsMultipleTiles() {
+        PeopleSpaceTile tile1 =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(1)
+                        .build();
+        PeopleSpaceTile tile2 =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        List<PeopleSpaceTile> actualList = PeopleSpaceUtils
+                .augmentTilesFromVisibleNotifications(List.of(tile1, tile2),
+                        mNotificationEntryManager);
+
+        assertThat(actualList.size()).isEqualTo(2);
+        assertThat(actualList.get(0).getNotificationContent().toString())
+                .isEqualTo(NOTIFICATION_TEXT_2);
+        assertThat(actualList.get(1).getNotificationContent().toString())
+                .isEqualTo(NOTIFICATION_TEXT_4);
+
+        verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index ef314ad..9470141 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -421,6 +421,7 @@
             throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
 
         Notification notificationWithoutMessagingStyle = new Notification.Builder(mContext)
                 .setContentTitle("TEST_TITLE")
@@ -429,15 +430,21 @@
                 .build();
         StatusBarNotification sbn = new SbnBuilder()
                 .setNotification(notificationWithoutMessagingStyle)
+                .setPkg(TEST_PACKAGE_A)
+                .setUid(0)
                 .build();
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
                 .setSbn(sbn)
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
-        verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        Bundle options = requireNonNull(mBundleArgumentCaptor.getValue());
+        assertThat((PeopleSpaceTile) options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE))
+                .isEqualTo(PERSON_TILE);
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index edaff5f..6c99efc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -32,13 +32,16 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -87,6 +90,8 @@
     OverlayManager mOverlayManager;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    OverlayManagerTransaction.Builder mTransactionBuilder;
 
     private ThemeOverlayApplier mManager;
 
@@ -94,7 +99,12 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
-                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
+                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+            @Override
+            protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+                return mTransactionBuilder;
+            }
+        };
         when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
                 .thenReturn(Lists.newArrayList(
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
@@ -148,9 +158,11 @@
     @Test
     public void allCategoriesSpecified_allEnabledExclusively() {
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        verify(mOverlayManager).commit(any());
 
         for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+                    eq(true), eq(TEST_USER.getIdentifier()));
         }
     }
 
@@ -160,11 +172,12 @@
 
         for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
             if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
-                verify(mOverlayManager).setEnabledExclusiveInCategory(
-                        entry.getValue(), UserHandle.SYSTEM);
+                verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(entry.getValue())),
+                        eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
             } else {
-                verify(mOverlayManager, never()).setEnabledExclusiveInCategory(
-                        entry.getValue(), UserHandle.SYSTEM);
+                verify(mTransactionBuilder, never()).setEnabled(
+                        eq(new OverlayIdentifier(entry.getValue())),
+                        eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
             }
         }
     }
@@ -177,8 +190,10 @@
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
 
         for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle);
+            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+                    eq(true), eq(TEST_USER.getIdentifier()));
+            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+                    eq(true), eq(newUserHandle.getIdentifier()));
         }
     }
 
@@ -199,12 +214,15 @@
         mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
 
         for (String overlayPackage : categoryToPackage.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+                    eq(true), eq(TEST_USER.getIdentifier()));
         }
-        verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS,
-                false, TEST_USER);
-        verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
-                false, TEST_USER);
+        verify(mTransactionBuilder).setEnabled(
+                eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)),
+                eq(false), eq(TEST_USER.getIdentifier()));
+        verify(mTransactionBuilder).setEnabled(
+                eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID)),
+                eq(false), eq(TEST_USER.getIdentifier()));
     }
 
     @Test
@@ -212,7 +230,9 @@
         mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES);
 
         for (String category : THEME_CATEGORIES) {
-            verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER);
+            verify(mTransactionBuilder).setEnabled(
+                    eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + category)), eq(false),
+                    eq(TEST_USER.getIdentifier()));
         }
     }
 
@@ -223,9 +243,12 @@
 
         mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
 
-        verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER);
-        verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category",
-                TEST_USER);
+        verify(mTransactionBuilder, never()).setEnabled(
+                eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
+                eq(TEST_USER.getIdentifier()));
+        verify(mTransactionBuilder, never()).setEnabled(
+                eq(new OverlayIdentifier("com.example.blah.category")), eq(true),
+                eq(TEST_USER.getIdentifier()));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index c743fd0..365c62c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -57,8 +57,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -88,7 +87,6 @@
     private static final String TEXT = "Hello World";
     private static final int MESSAGE_RES_ID = R.id.message;
 
-    private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock());
     private Context mContextSpy;
     private ToastUI mToastUI;
     @Mock private LayoutInflater mLayoutInflater;
@@ -99,6 +97,7 @@
     @Mock private PluginManager mPluginManager;
     @Mock private DumpManager mDumpManager;
     @Mock private ToastLogger mToastLogger;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Mock private ITransientNotificationCallback mCallback;
     @Captor private ArgumentCaptor<View> mViewCaptor;
@@ -107,12 +106,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        // This is because inflate will result in WindowManager (WM) calls, which will fail since we
-        // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM.
-        View view = ToastPresenter.getTextToastView(mContext, TEXT);
-        when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view);
-        mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater);
+        when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(
+                ToastPresenter.getTextToastView(mContext, TEXT));
+        when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false);
 
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
         mContextSpy = spy(mContext);
@@ -120,8 +116,8 @@
 
         doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
         mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
-                mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager),
-                mFakeDelayableExecutor, mToastLogger);
+                mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager,
+                mDumpManager, mFeatureFlags), mToastLogger);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 76269dd..f1fc0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,8 +89,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -297,7 +295,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 5340ff7..9e10b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -261,7 +261,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 25f4abe..903a071 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -19,12 +19,12 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
@@ -147,15 +147,15 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this));
         mMultiFingerGestures.add(
-                new MultiFingerMultiTapAndHold(
-                        mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, this));
-        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTapAndHold(
                         mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, this));
         // Three-finger taps.
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP, this));
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 67f654e..6d72ca7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1492,7 +1492,7 @@
                 }
                 final Dataset dataset = (Dataset) result;
                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
-                if (!isPinnedDataset(oldDataset)) {
+                if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 }
                 autoFill(requestId, datasetIdx, dataset, false);
@@ -1513,6 +1513,21 @@
     }
 
     /**
+     * Returns whether the dataset returned from the authentication result is ephemeral or not.
+     * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
+     * information.
+     */
+    private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset,
+            @NonNull Bundle authResultData) {
+        if (authResultData.containsKey(
+                AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+            return authResultData.getBoolean(
+                    AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET);
+        }
+        return isPinnedDataset(oldDataset);
+    }
+
+    /**
      * A dataset can potentially have multiple fields, and it's possible that some of the fields'
      * has inline presentation and some don't. It's also possible that some of the fields'
      * inline presentation is pinned and some isn't. So the concept of whether a dataset is
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 96b69dc..10b00d3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -161,6 +161,8 @@
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "CompanionDeviceManagerService";
 
+    private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
+
     private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
     private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
 
@@ -170,6 +172,7 @@
     private static final String XML_ATTR_DEVICE = "device";
     private static final String XML_ATTR_PROFILE = "profile";
     private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
+    private static final String XML_ATTR_TIME_APPROVED = "time_approved";
     private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";
 
     private final CompanionDeviceManagerImpl mImpl;
@@ -606,7 +609,8 @@
                             association.getDeviceMacAddress(),
                             association.getPackageName(),
                             association.getDeviceProfile(),
-                            active /* notifyOnDeviceNearby */);
+                            active, /* notifyOnDeviceNearby */
+                            association.getTimeApprovedMs());
                 } else {
                     return association;
                 }
@@ -639,6 +643,15 @@
         }
 
         @Override
+        public boolean canPairWithoutPrompt(
+                String packageName, String deviceMacAddress, int userId) {
+            return CollectionUtils.any(
+                    getAllAssociations(userId, packageName, deviceMacAddress),
+                    a -> System.currentTimeMillis() - a.getTimeApprovedMs()
+                            < PAIR_WITHOUT_PROMPT_WINDOW_MS);
+        }
+
+        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
@@ -877,6 +890,8 @@
                                     Boolean.toString(
                                             association.isNotifyOnDeviceNearby()));
                         }
+                        tag.attribute(null, XML_ATTR_TIME_APPROVED,
+                                Long.toString(association.getTimeApprovedMs()));
                         tag.endTag(null, XML_TAG_ASSOCIATION);
                     });
 
@@ -921,7 +936,6 @@
         }
     }
 
-    @Nullable
     private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
         return CollectionUtils.filter(
                 getAllAssociations(userId),
@@ -941,6 +955,14 @@
         }
     }
 
+    private Set<Association> getAllAssociations(
+            int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
+        return CollectionUtils.filter(
+                getAllAssociations(userId),
+                a -> Objects.equals(packageFilter, a.getPackageName())
+                        && Objects.equals(addressFilter, a.getDeviceMacAddress()));
+    }
+
     private Set<Association> readAllAssociations(int userId) {
         final AtomicFile file = getStorageFileForUser(userId);
 
@@ -962,12 +984,14 @@
                     final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE);
                     final boolean persistentGrants = Boolean.valueOf(
                             parser.getAttributeValue(null, XML_ATTR_NOTIFY_DEVICE_NEARBY));
+                    final long timeApproved = parseLongOrDefault(
+                            parser.getAttributeValue(null, XML_ATTR_TIME_APPROVED), 0L);
 
                     if (appPackage == null || deviceAddress == null) continue;
 
                     result = ArrayUtils.add(result,
                             new Association(userId, deviceAddress, appPackage,
-                                    profile, persistentGrants));
+                                    profile, persistentGrants, timeApproved));
                 }
                 return result;
             } catch (XmlPullParserException | IOException e) {
@@ -1293,6 +1317,15 @@
         return result;
     }
 
+    private static long parseLongOrDefault(String str, long def) {
+        try {
+            return Long.parseLong(str);
+        } catch (NumberFormatException e) {
+            Log.w(LOG_TAG, "Failed to parse", e);
+            return def;
+        }
+    }
+
     private class ShellCmd extends ShellCommand {
         public static final String USAGE = "help\n"
                 + "list USER_ID\n"
@@ -1321,7 +1354,8 @@
                         int userId = getNextArgInt();
                         String pkg = getNextArgRequired();
                         String address = getNextArgRequired();
-                        addAssociation(new Association(userId, address, pkg, null, false));
+                        addAssociation(new Association(userId, address, pkg, null, false,
+                                System.currentTimeMillis()));
                     }
                     break;
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 96cfe02..25890b0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -82,6 +82,7 @@
         ":framework_native_aidl",
         ":gsiservice_aidl",
         ":idmap2_aidl",
+        ":idmap2_core_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
         ":storaged_aidl",
@@ -104,7 +105,6 @@
         "android.hardware.power-V1-java",
         "android.hardware.power-V1.0-java",
         "android.hardware.vibrator-V2-java",
-        "android.net.ipsec.ike.stubs.module_lib",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-permission.stubs.system_server",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 737a9e4..342208c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -998,6 +998,18 @@
     public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
 
     /**
+     * Register to listen for loading progress of an installed package.
+     * The listener is automatically unregistered when the app is fully loaded.
+     * @param packageName The name of the installed package
+     * @param callback To loading reporting progress
+     * @param userId The user under which to check.
+     * @return Whether the registration was successful. It can fail if the package has not been
+     *          installed yet.
+     */
+    public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName,
+            @NonNull InstalledLoadingProgressCallback callback, int userId);
+
+    /**
      * Returns the string representation of a known package. For example,
      * {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard.
      *
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 958c15c..e996eb4 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
 import java.util.Collection;
 
@@ -37,6 +38,9 @@
      */
     public abstract String[] getMobileIfaces();
 
+    /** Returns CPU times for system server thread groups. */
+    public abstract SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes();
+
     /**
      * Inform battery stats how many deferred jobs existed when the app got launched and how
      * long ago was the last job execution for the app.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2f88351..277152d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -132,12 +132,14 @@
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
 import android.net.TetheringManager;
+import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
@@ -5747,6 +5749,7 @@
                 throw new SecurityException("Insufficient permissions to specify legacy type");
             }
         }
+        final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
         final int callingUid = mDeps.getCallingUid();
         final NetworkRequest.Type reqType;
         try {
@@ -5757,11 +5760,15 @@
         switch (reqType) {
             case TRACK_DEFAULT:
                 // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
-                // is unused and will be replaced by the one from the default network request.
-                // This allows callers to keep track of the system default network.
+                // is unused and will be replaced by ones appropriate for the caller.
+                // This allows callers to keep track of the default network for their app.
                 networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
                 enforceAccessPermission();
                 break;
+            case TRACK_SYSTEM_DEFAULT:
+                enforceSettingsPermission();
+                networkCapabilities = new NetworkCapabilities(defaultNc);
+                break;
             case BACKGROUND_REQUEST:
                 enforceNetworkStackOrSettingsPermission();
                 // Fall-through since other checks are the same with normal requests.
@@ -5780,6 +5787,7 @@
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
+
         // Set the UID range for this request to the single UID of the requester, or to an empty
         // set of UIDs if the caller has the appropriate permission and UIDs have not been set.
         // This will overwrite any allowed UIDs in the requested capabilities. Though there
@@ -5799,6 +5807,16 @@
                 new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
         if (DBG) log("requestNetwork for " + nri);
 
+        // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
+        // copied from the default request above. (This is necessary to ensure, for example, that
+        // the callback does not leak sensitive information to unprivileged apps.) Check that the
+        // changes don't alter request matching.
+        if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
+                (!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
+            Log.wtf(TAG, "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+                    + networkCapabilities + " vs. " + defaultNc);
+        }
+
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
         if (timeoutMs > 0) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
@@ -6389,20 +6407,18 @@
                     Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis()));
         }
 
-        // Prioritize the user portal URL from the network agent.
-        if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null
-                || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) {
-            captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl());
+        // Prioritize the user portal URL from the network agent if the source is authenticated.
+        if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource()
+                != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+            captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(),
+                    apiData.getUserPortalUrlSource());
         }
-        // Prioritize the venue information URL from the network agent.
-        if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null
-                || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) {
-            captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl());
-
-            // Note that venue friendly name can only come from the network agent because it is not
-            // in use in RFC8908. However, if using the Capport venue URL, make sure that the
-            // friendly name is not set from the network agent.
-            captivePortalBuilder.setVenueFriendlyName(null);
+        // Prioritize the venue information URL from the network agent if the source is
+        // authenticated.
+        if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource()
+                != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+            captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(),
+                    apiData.getVenueInfoUrlSource());
         }
         return captivePortalBuilder.build();
     }
@@ -8462,22 +8478,11 @@
         }
     }
 
-    /**
-     * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
-     * for testing.
-     */
-    private Vpn enforceActiveVpnOrNetworkStackPermission() {
-        if (checkNetworkStackPermission()) {
-            return null;
-        }
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            if (vpn != null) {
-                return vpn;
-            }
-        }
-        throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
-                + "permission");
+    private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+        if (vpn == null) return VpnManager.TYPE_VPN_NONE;
+        final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
+        if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
+        return ((VpnTransportInfo) ti).type;
     }
 
     /**
@@ -8487,14 +8492,6 @@
      * connection is not found.
      */
     public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
-        final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
-
-        // Only VpnService based VPNs should be able to get this information.
-        if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) {
-            throw new SecurityException(
-                    "getConnectionOwnerUid() not allowed for non-VpnService VPNs");
-        }
-
         if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
             throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
         }
@@ -8502,8 +8499,15 @@
         final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
                 connectionInfo.local, connectionInfo.remote);
 
-        /* Filter out Uids not associated with the VPN. */
-        if (vpn != null && !vpn.appliesToUid(uid)) {
+        if (uid == INVALID_UID) return uid;  // Not found.
+
+        // Connection owner UIDs are visible only to the network stack and to the VpnService-based
+        // VPN, if any, that applies to the UID that owns the connection.
+        if (checkNetworkStackPermission()) return uid;
+
+        final NetworkAgentInfo vpn = getVpnForUid(uid);
+        if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
+                || vpn.networkCapabilities.getOwnerUid() != Binder.getCallingUid()) {
             return INVALID_UID;
         }
 
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
index c56cef2..a83c981 100644
--- a/services/core/java/com/android/server/EntropyMixer.java
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -16,12 +16,6 @@
 
 package com.android.server;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -33,10 +27,15 @@
 import android.os.SystemProperties;
 import android.util.Slog;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
 /**
  * A service designed to load and periodically save &quot;randomness&quot;
- * for the Linux kernel RNG and to mix in data from Hardware RNG (if present)
- * into the Linux RNG.
+ * for the Linux kernel RNG.
  *
  * <p>When a Linux system starts up, the entropy pool associated with
  * {@code /dev/random} may be in a fairly predictable state.  Applications which
@@ -45,15 +44,8 @@
  * this effect, it's helpful to carry the entropy pool information across
  * shutdowns and startups.
  *
- * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW
- * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever
- * EntropyMixer periodically runs to save a block of output from Linux RNG on
- * disk. This mixing is done in a way that does not increase the Linux RNG's
- * entropy estimate is not increased. This is to avoid having to trust/verify
- * the quality and authenticity of the &quot;randomness&quot; of the HW RNG.
- *
  * <p>This class was modeled after the script in the
- * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">
+ * <a href="https://man7.org/linux/man-pages/man4/random.4.html">
  * random(4) manual page</a>.
  */
 public class EntropyMixer extends Binder {
@@ -64,7 +56,6 @@
     private static final long START_NANOTIME = System.nanoTime();
 
     private final String randomDevice;
-    private final String hwRandomDevice;
     private final String entropyFile;
 
     /**
@@ -80,7 +71,6 @@
                 Slog.e(TAG, "Will not process invalid message");
                 return;
             }
-            addHwRandomEntropy();
             writeEntropy();
             scheduleEntropyWriter();
         }
@@ -94,25 +84,21 @@
     };
 
     public EntropyMixer(Context context) {
-        this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random");
+        this(context, getSystemDir() + "/entropy.dat", "/dev/urandom");
     }
 
     /** Test only interface, not for public use */
     public EntropyMixer(
             Context context,
             String entropyFile,
-            String randomDevice,
-            String hwRandomDevice) {
+            String randomDevice) {
         if (randomDevice == null) { throw new NullPointerException("randomDevice"); }
-        if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); }
         if (entropyFile == null) { throw new NullPointerException("entropyFile"); }
 
         this.randomDevice = randomDevice;
-        this.hwRandomDevice = hwRandomDevice;
         this.entropyFile = entropyFile;
         loadInitialEntropy();
         addDeviceSpecificEntropy();
-        addHwRandomEntropy();
         writeEntropy();
         scheduleEntropyWriter();
         IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
@@ -192,23 +178,6 @@
         }
     }
 
-    /**
-     * Mixes in the output from HW RNG (if present) into the Linux RNG.
-     */
-    private void addHwRandomEntropy() {
-        if (!new File(hwRandomDevice).exists()) {
-            // HW RNG not present/exposed -- ignore
-            return;
-        }
-
-        try {
-            RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
-            Slog.i(TAG, "Added HW RNG output to entropy pool");
-        } catch (IOException e) {
-            Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
-        }
-    }
-
     private static String getSystemDir() {
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 00d8b0f..d10cf4d 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -30,6 +30,7 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -147,14 +148,15 @@
     private int getAllowedUid(int userHandle) {
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
-        PackageManager pm = mContext.getPackageManager();
         int allowedUid = -1;
-        try {
-            allowedUid = pm.getPackageUidAsUser(allowedPackage,
-                    PackageManager.MATCH_SYSTEM_ONLY, userHandle);
-        } catch (PackageManager.NameNotFoundException e) {
-            // not expected
-            Slog.e(TAG, "not able to find package " + allowedPackage, e);
+        if (!TextUtils.isEmpty(allowedPackage)) {
+            try {
+                allowedUid = mContext.getPackageManager().getPackageUidAsUser(
+                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+            } catch (PackageManager.NameNotFoundException e) {
+                // not expected
+                Slog.e(TAG, "not able to find package " + allowedPackage, e);
+            }
         }
         return allowedUid;
     }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 4dce59f..27210da 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -641,21 +641,34 @@
         }
 
         boolean isVcnManagedNetwork = false;
+        boolean isRestrictedCarrierWifi = false;
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             synchronized (mLock) {
                 ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
 
                 Vcn vcn = mVcns.get(subGroup);
-                if (vcn != null && vcn.isActive()) {
-                    isVcnManagedNetwork = true;
+                if (vcn != null) {
+                    if (vcn.isActive()) {
+                        isVcnManagedNetwork = true;
+                    }
+
+                    if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                        // Carrier WiFi always restricted if VCN exists (even in safe mode).
+                        isRestrictedCarrierWifi = true;
+                    }
                 }
             }
         }
+
         if (isVcnManagedNetwork) {
             networkCapabilities.removeCapability(
                     NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
         }
 
+        if (isRestrictedCarrierWifi) {
+            networkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
+
         return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9382e1a..f0f29a9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10700,13 +10700,13 @@
             long kernelUsed = memInfo.getKernelUsedSizeKb();
             final long ionHeap = Debug.getIonHeapsSizeKb();
             final long ionPool = Debug.getIonPoolsSizeKb();
+            final long dmabufMapped = Debug.getDmabufMappedSizeKb();
             if (ionHeap >= 0 && ionPool >= 0) {
-                final long ionMapped = Debug.getIonMappedSizeKb();
-                final long ionUnmapped = ionHeap - ionMapped;
+                final long ionUnmapped = ionHeap - dmabufMapped;
                 pw.print("      ION: ");
                         pw.print(stringifyKBSize(ionHeap + ionPool));
                         pw.print(" (");
-                        pw.print(stringifyKBSize(ionMapped));
+                        pw.print(stringifyKBSize(dmabufMapped));
                         pw.print(" mapped + ");
                         pw.print(stringifyKBSize(ionUnmapped));
                         pw.print(" unmapped + ");
@@ -10715,11 +10715,34 @@
                 // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
                 // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
                 kernelUsed += ionHeap;
+            } else {
+                final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
+                if (totalExportedDmabuf >= 0) {
+                    final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
+                    pw.print("DMA-BUF: ");
+                    pw.print(stringifyKBSize(totalExportedDmabuf));
+                    pw.print(" (");
+                    pw.print(stringifyKBSize(dmabufMapped));
+                    pw.print(" mapped + ");
+                    pw.print(stringifyKBSize(dmabufUnmapped));
+                    pw.println(" unmapped)");
+                    kernelUsed += totalExportedDmabuf;
+                }
+                final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
+                if (totalDmabufHeapPool >= 0) {
+                    pw.print("DMA-BUF Heaps pool: ");
+                    pw.println(stringifyKBSize(totalDmabufHeapPool));
+                }
             }
             final long gpuUsage = Debug.getGpuTotalUsageKb();
             if (gpuUsage >= 0) {
                 pw.print("      GPU: "); pw.println(stringifyKBSize(gpuUsage));
             }
+
+            /*
+             * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+             * memInfo.getCachedSizeKb().
+             */
             final long lostRAM = memInfo.getTotalSizeKb()
                     - (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
                     - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 8f11a5a..3ff5872 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1512,15 +1512,29 @@
         final long ionHeap = Debug.getIonHeapsSizeKb();
         final long ionPool = Debug.getIonPoolsSizeKb();
         if (ionHeap >= 0 && ionPool >= 0) {
-            final long ionMapped = Debug.getIonMappedSizeKb();
-            final long ionUnmapped = ionHeap - ionMapped;
             memInfoBuilder.append("       ION: ");
             memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
             memInfoBuilder.append("\n");
             // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
             // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
             kernelUsed += ionHeap;
+        } else {
+            final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
+            if (totalExportedDmabuf >= 0) {
+                memInfoBuilder.append("DMA-BUF: ");
+                memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf));
+                memInfoBuilder.append("\n");
+                kernelUsed += totalExportedDmabuf;
+            }
+
+            final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
+            if (totalDmabufHeapPool >= 0) {
+                memInfoBuilder.append("DMA-BUF Heaps pool: ");
+                memInfoBuilder.append(stringifyKBSize(totalDmabufHeapPool));
+                memInfoBuilder.append("\n");
+            }
         }
+
         final long gpuUsage = Debug.getGpuTotalUsageKb();
         if (gpuUsage >= 0) {
             memInfoBuilder.append("       GPU: ");
@@ -1531,6 +1545,11 @@
         memInfoBuilder.append(stringifyKBSize(
                                   totalPss - cachedPss + kernelUsed));
         memInfoBuilder.append("\n");
+
+        /*
+         * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+         * memInfo.getCachedSizeKb().
+         */
         memInfoBuilder.append("  Lost RAM: ");
         memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
                 - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index c6947c2d..b994389 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.am;
 
-import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY;
-
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
@@ -39,13 +37,12 @@
 import android.telephony.TelephonyManager;
 import android.util.IntArray;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -148,13 +145,13 @@
     private WifiActivityEnergyInfo mLastWifiInfo =
             new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
 
-    /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */
+    /**
+     * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
+     * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
+     */
+    // TODO: Hook this up (it isn't used yet)
     @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null;
-
-    /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */
-    @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null;
+    private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
     /** Snapshot of measured energies, or null if no measured energies are supported. */
     @GuardedBy("mWorkerLock")
@@ -204,18 +201,26 @@
             mWifiManager = wm;
             mTelephony = tm;
             mPowerStatsInternal = psi;
+
+            boolean[] supportedStdBuckets = null;
+            int numCustomBuckets = 0;
             if (mPowerStatsInternal != null) {
-                populateEnergyConsumerSubsystemMapsLocked();
-                final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
-                mMeasuredEnergySnapshot = initialMeasuredEnergies == null
-                        ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
-                final boolean[] supportedStdBuckets
-                        = getSupportedEnergyBuckets(initialMeasuredEnergies);
-                final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
-                synchronized (mStats) {
-                    mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+                final SparseArray<EnergyConsumer> idToConsumer
+                        = populateEnergyConsumerSubsystemMapsLocked();
+                if (idToConsumer != null) {
+                    mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+                    final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
+                    // According to spec, initialEcrs will include 0s for consumers that haven't
+                    // used any energy yet, as long as they are supported; however, attributed uid
+                    // energies will be absent if their energy is 0.
+                    mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+                    numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
+                    supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
             }
+            synchronized (mStats) {
+                mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+            }
         }
     }
 
@@ -568,7 +573,9 @@
         } catch (ExecutionException e) {
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
-        final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null :
+
+        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
+                mMeasuredEnergySnapshot == null ? null :
                 mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -576,6 +583,7 @@
         final long elapsedRealtimeUs = elapsedRealtime * 1000;
         final long uptimeUs = uptime * 1000;
 
+        // Now that we have finally received all the data, we can tell mStats about it.
         synchronized (mStats) {
             mStats.addHistoryEventLocked(
                     elapsedRealtime,
@@ -601,10 +609,21 @@
             }
 
             // Inform mStats about each applicable measured energy.
-            if (energyDeltas != null) {
-                final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L);
-                // Always pass in what BatteryExternalStatsWorker thinks screenState is.
-                mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+            if (measuredEnergyDeltas != null) {
+                final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ;
+                if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) {
+                    // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
+                    mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+                }
+            }
+            // Inform mStats about each applicable custom energy bucket.
+            if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) {
+                // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
+                for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) {
+                    long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord];
+                    SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord];
+                    mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies);
+                }
             }
 
             if (bluetoothInfo != null) {
@@ -621,7 +640,8 @@
 
         if (wifiInfo != null) {
             if (wifiInfo.isValid()) {
-                // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L);
+                // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ
+                //               .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
                 mStats.updateWifiState(extractDeltaLocked(wifiInfo)
                         /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
             } else {
@@ -740,21 +760,23 @@
     }
 
     /**
-     * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
+     * Map the {@link EnergyConsumerType}s in the given energyArray to
      * their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s.
      * Does not include custom energy buckets (which are always, by definition, supported).
      *
      * @return array with true for index i if standard energy bucket i is supported.
      */
-    private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    private static @Nullable boolean[] getSupportedEnergyBuckets(
+            SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) {
             return null;
         }
         final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
-        final int size = energyArray.size();
-        for (int energyIdx = 0; energyIdx < size; energyIdx++) {
-            switch (energyArray.getSubsystem(energyIdx)) {
-                case MeasuredEnergyArray.SUBSYSTEM_DISPLAY:
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            switch (consumer.type) {
+                case EnergyConsumerType.DISPLAY:
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true;
@@ -764,71 +786,22 @@
         return buckets;
     }
 
-    /**
-     * Get a {@link MeasuredEnergyArray} with the latest
-     * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot.
-     *
-     * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link
-     * EnergyConsumerResult}[]
-     */
+    /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
     @GuardedBy("mWorkerLock")
-    @VisibleForTesting
-    public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
-        final EnergyConsumerResult[] results;
+    private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
         try {
-            results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
+            return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
                     .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
             return null;
         }
-        if (results == null) return null;
-        final int size = results.length;
-        final int[] subsystems = new int[size];
-        final long[] energyUJ = new long[size];
-
-        int count = 0;
-        for (int i = 0; i < size; i++) {
-            final EnergyConsumerResult consumer = results[i];
-            final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
-                    MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
-            if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
-            subsystems[count] = subsystem;
-            energyUJ[count] = consumer.energyUWs;
-            count++;
-        }
-        final int arraySize = count;
-        return new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return subsystems[index];
-            }
-
-            @Override
-            public long getEnergy(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return energyUJ[index];
-            }
-
-            @Override
-            public int size() {
-                return arraySize;
-            }
-        };
     }
 
-    /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */
+    /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) {
+    private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
+    {
         if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
@@ -836,12 +809,13 @@
             return getEnergyConsumptionData();
         }
 
-        final List<Integer> energySubsystems = new ArrayList<>();
+        final List<Integer> energyConsumerIds = new ArrayList<>();
         if ((flags & UPDATE_DISPLAY) != 0) {
-            addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY);
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
         }
         // TODO: Wifi, Bluetooth, etc., go here
-        if (energySubsystems.isEmpty()) {
+
+        if (energyConsumerIds.isEmpty()) {
             return null;
         }
         // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
@@ -849,59 +823,48 @@
     }
 
     @GuardedBy("mWorkerLock")
-    private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds,
-            @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) {
-        if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) {
-            energyConsumerIds.add(consumerId);
-        }
+    private void addEnergyConsumerIdLocked(
+            List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
+        final int consumerId = 0; // TODO: Use mEnergyConsumerTypeToIdMap to get this
+        energyConsumerIds.add(consumerId);
     }
 
+    /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
     @GuardedBy("mWorkerLock")
-    private void populateEnergyConsumerSubsystemMapsLocked() {
+    private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() {
         if (mPowerStatsInternal == null) {
-            // PowerStatsInternal unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+            return null;
         }
         final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
-        if (energyConsumers == null) {
-            // EnergyConsumer data unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+        if (energyConsumers == null || energyConsumers.length == 0) {
+            return null;
         }
 
-        final int length = energyConsumers.length;
-        if (length == 0) {
-            // EnergyConsumer array empty, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
-        } else {
-            mEnergyConsumerToSubsystemMap = new SparseIntArray(length);
-            mSubsystemToEnergyConsumerMap = new SparseIntArray(length);
-        }
+        // TODO: Initialize typeToIds
+        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+        // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
+
+        // Maps id -> EnergyConsumer (1:1 map)
+        final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
 
         // Add all expected EnergyConsumers to the maps
-        for (int i = 0; i < length; i++) {
-            final EnergyConsumer consumer = energyConsumers[i];
-            switch (consumer.type) {
-                case EnergyConsumerType.DISPLAY:
-                    if (consumer.ordinal == 0) {
-                        mEnergyConsumerToSubsystemMap.put(consumer.id,
-                                MeasuredEnergyArray.SUBSYSTEM_DISPLAY);
-                        mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY,
-                                consumer.id);
-                    } else {
-                        Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal
-                                + ") for EnergyConsumerType.DISPLAY");
-                    }
-                    break;
-                default:
-                    Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")");
+        for (final EnergyConsumer consumer : energyConsumers) {
+            // Check for inappropriate ordinals
+            if (consumer.ordinal != 0) {
+                switch (consumer.type) {
+                    case EnergyConsumerType.OTHER:
+                    case EnergyConsumerType.CPU_CLUSTER:
+                        break;
+                    default:
+                        Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
+                                + consumer.ordinal + " for type " + consumer.type);
+                        continue; // Ignore this consumer
+                }
             }
-
+            idToConsumer.put(consumer.id, consumer);
+            // TODO: Also populate typeToIds map
         }
+        // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap.
+        return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 773e313..6500f6d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -72,6 +72,7 @@
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ParseUtils;
@@ -126,15 +127,20 @@
                     .onMalformedInput(CodingErrorAction.REPLACE)
                     .onUnmappableCharacter(CodingErrorAction.REPLACE)
                     .replaceWith("?");
-    private static final int MAX_LOW_POWER_STATS_SIZE = 4096;
+    private static final int MAX_LOW_POWER_STATS_SIZE = 8192;
     private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
+    private static final String EMPTY = "Empty";
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Object mLock = new Object();
 
+    private final Object mPowerStatsLock = new Object();
+    @GuardedBy("mPowerStatsLock")
     private PowerStatsInternal mPowerStatsInternal = null;
+    @GuardedBy("mPowerStatsLock")
     private Map<Integer, String> mEntityNames = new HashMap();
+    @GuardedBy("mPowerStatsLock")
     private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
 
     @GuardedBy("mStats")
@@ -172,13 +178,6 @@
             };
 
     private void populatePowerEntityMaps() {
-        if (mPowerStatsInternal == null) {
-            // PowerStatsInternal unavailable, don't bother populating maps.
-            mEntityNames = null;
-            mStateNames = null;
-            return;
-        }
-
         PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
         if (entities == null) {
             return;
@@ -202,6 +201,12 @@
      */
     @Override
     public void fillLowPowerStats(RpmStats rpmStats) {
+        synchronized (mPowerStatsLock) {
+            if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) {
+                return;
+            }
+        }
+
         final StateResidencyResult[] results;
         try {
             results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
@@ -237,16 +242,22 @@
 
     @Override
     public String getSubsystemLowPowerStats() {
+        synchronized (mPowerStatsLock) {
+            if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) {
+                return EMPTY;
+            }
+        }
+
         final StateResidencyResult[] results;
         try {
             results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
                     .get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to getStateResidencyAsync", e);
-            return "Empty";
+            return EMPTY;
         }
 
-        if (results.length == 0) return "Empty";
+        if (results.length == 0) return EMPTY;
 
         int charsLeft = MAX_LOW_POWER_STATS_SIZE;
         StringBuilder builder = new StringBuilder("SubsystemPowerState");
@@ -322,9 +333,14 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
         }
-        mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
-        if (mPowerStatsInternal != null) {
-            populatePowerEntityMaps();
+
+        synchronized (mPowerStatsLock) {
+            mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+            if (mPowerStatsInternal != null) {
+                populatePowerEntityMaps();
+            } else {
+                Slog.e(TAG, "Could not register PowerStatsInternal");
+            }
         }
 
         Watchdog.getInstance().addMonitor(this);
@@ -342,6 +358,11 @@
         }
 
         @Override
+        public SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes() {
+            return mStats.getSystemServiceCpuThreadTimes();
+        }
+
+        @Override
         public void noteJobsDeferred(int uid, int numDeferred, long sinceLast) {
             if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
             BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7e65434..8b6fabd 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -967,6 +967,12 @@
         }
     }
 
+    static String broadcastDescription(BroadcastRecord r, ComponentName component) {
+        return r.intent.toString()
+                + " from " + r.callerPackage + " (pid=" + r.callingPid
+                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
+    }
+
     final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
         BroadcastRecord r;
 
@@ -1349,14 +1355,18 @@
                         < brOptions.getMinManifestReceiverApiLevel() ||
                 info.activityInfo.applicationInfo.targetSdkVersion
                         > brOptions.getMaxManifestReceiverApiLevel())) {
+            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+                    + " but delivery restricted to ["
+                    + brOptions.getMinManifestReceiverApiLevel() + ", "
+                    + brOptions.getMaxManifestReceiverApiLevel()
+                    + "] broadcasting " + broadcastDescription(r, component));
             skip = true;
         }
         if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
             Slog.w(TAG, "Association not allowed: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+                    + broadcastDescription(r, component));
             skip = true;
         }
         if (!skip) {
@@ -1364,9 +1374,7 @@
                     r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
             if (skip) {
                 Slog.w(TAG, "Firewall blocked: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+                        + broadcastDescription(r, component));
             }
         }
         int perm = mService.checkComponentPermission(info.activityInfo.permission,
@@ -1375,18 +1383,12 @@
         if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
             if (!info.activityInfo.exported) {
                 Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ")"
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid
-                        + " due to receiver " + component.flattenToShortString());
+                        + broadcastDescription(r, component)
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
             } else {
                 Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ")"
-                        + " requires " + info.activityInfo.permission
-                        + " due to receiver " + component.flattenToShortString());
+                        + broadcastDescription(r, component)
+                        + " requires " + info.activityInfo.permission);
             }
             skip = true;
         } else if (!skip && info.activityInfo.permission != null) {
@@ -1396,13 +1398,9 @@
                     "Broadcast delivered to " + info.activityInfo.name)
                     != AppOpsManager.MODE_ALLOWED) {
                 Slog.w(TAG, "Appop Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid="
-                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + broadcastDescription(r, component)
                         + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission)
-                        + " due to registered receiver "
-                        + component.flattenToShortString());
+                                info.activityInfo.permission));
                 skip = true;
             }
         }
@@ -1520,7 +1518,7 @@
                         + info.activityInfo.packageName, e);
             }
             if (!isAvailable) {
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                Slog.w(TAG_BROADCAST,
                         "Skipping delivery to " + info.activityInfo.packageName + " / "
                         + info.activityInfo.applicationInfo.uid
                         + " : package no longer available");
@@ -1536,6 +1534,9 @@
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                     info.activityInfo.packageName, UserHandle.getUserId(
                             info.activityInfo.applicationInfo.uid))) {
+                Slog.w(TAG_BROADCAST,
+                        "Skipping delivery: permission review required for "
+                                + broadcastDescription(r, component));
                 skip = true;
             }
         }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c18031f..7bdf43c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -538,6 +538,12 @@
     private static native int getBinderFreezeInfo(int pid);
 
     /**
+     * Returns the path to be checked to verify whether the freezer is supported by this system.
+     * @return absolute path to the file
+     */
+    private static native String getFreezerCheckPath();
+
+    /**
      * Determines whether the freezer is supported by this system
      */
     public static boolean isFreezerSupported() {
@@ -545,11 +551,15 @@
         FileReader fr = null;
 
         try {
-            fr = new FileReader("/sys/fs/cgroup/uid_0/cgroup.freeze");
+            fr = new FileReader(getFreezerCheckPath());
             char state = (char) fr.read();
 
             if (state == '1' || state == '0') {
                 supported = true;
+                // This is a workaround after reverting the cgroup v2 uid/pid hierarchy due to
+                // http://b/179006802.
+                // TODO: remove once the uid/pid hierarchy is restored
+                enableFreezerInternal(true);
             } else {
                 Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
             }
@@ -1149,7 +1159,7 @@
                     }
                     return;
                 }
-            } catch (IOException e) {
+            } catch (Exception e) {
                 Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
                         + "): " + e);
                 return;
@@ -1244,7 +1254,7 @@
                         }
                     }
                 }
-            } catch (IOException e) {
+            } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                 synchronized (mAm) {
                     synchronized (mProcLock) {
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index b915c0c..9e0aa32 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -17,132 +17,260 @@
 package com.android.server.am;
 
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 
 /**
- * Keeps snapshots of data from previously pulled MeasuredEnergyArrays.
+ * Keeps snapshots of data from previously pulled EnergyConsumerResults.
  */
 @VisibleForTesting
 public class MeasuredEnergySnapshot {
     private static final String TAG = "MeasuredEnergySnapshot";
 
-    private static final long UNAVAILABLE = -1;
+    public static final long UNAVAILABLE = -1L;
+
+    /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
+    private final SparseArray<EnergyConsumer> mEnergyConsumers;
+
+    /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private final int mNumOtherOrdinals;
 
     /**
-     * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated.
+     * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
+     * each {@link EnergyConsumer} was updated.
      *
-     * Note that the snapshots for different subsystems may have been taken at different times.
+     * Note that the snapshots for different ids may have been taken at different times.
+     * Note that energies for all existing ids are stored here, including each ordinal of type
+     * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
      *
-     * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported).
+     * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
      */
-    private final long[] mMeasuredEnergySnapshots;
+    private final SparseLongArray mMeasuredEnergySnapshots;
 
     /**
-     * Constructor that initializes to the given energyArray;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
+     * {@link EnergyConsumerType#OTHER} was updated.
+     * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
+     * uid to an energy (UJ). That is,
+     * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
+     *
+     * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
+     * If an id is present but a uid is not present, that uid's energy is 0.
      */
-    public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) {
-        this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray);
+    private final SparseArray<SparseLongArray> mAttributionSnapshots;
+
+    /**
+     * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
+     * exist and what their details are.
+     */
+    MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+        mEnergyConsumers = idToConsumerMap;
+        mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+
+        mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
+        mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
     }
 
     /**
-     * Constructor (for testing) that initializes to the given energyArray and numSubsystems;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
+     * custom energy buckets supported by the device.
      */
-    @VisibleForTesting
-    MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) {
-        if (initialEnergyArray.size() > numSubsystems) {
-            throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size()
-                    + " subsystems, which exceeds the maximum allowed of " + numSubsystems);
-        }
-        mMeasuredEnergySnapshots = new long[numSubsystems];
-        Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE);
-        fillGivenSubsystems(initialEnergyArray);
+    public int getNumOtherOrdinals() {
+        return mNumOtherOrdinals;
     }
 
-    /**
-     * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their
-     * energy values from energyArray.
-     */
-    private void fillGivenSubsystems(MeasuredEnergyArray energyArray) {
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int subsystem = energyArray.getSubsystem(i);
-            mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i);
-        }
+    /** Class for returning measured energy delta data. */
+    static class MeasuredEnergyDeltaData {
+        /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
+        public long displayEnergyUJ = UNAVAILABLE;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
+        public @Nullable long[] otherTotalEnergyUJ = null;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
+        public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
     }
 
     /**
      * Update with the some freshly measured energies and return the difference (delta)
      * between the previously stored values and the passed-in values.
      *
-     * @param energyArray measured energy array for some (possibly not all) subsystems.
+     * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
+     *             Consumers that are not present are ignored (they are *not* treated as 0).
      *
-     * @return a map from the updated subsystems to their corresponding energy deltas.
-     *         Subsystems not present in energyArray will not appear.
-     *         Subsystems with no difference in energy will not appear.
-     *         Returns null, if energyArray is null.
+     * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+     *         their corresponding energy deltas.
+     *         Fields with no interesting data (consumers not present in ecrs or with no energy
+     *         difference) will generally be left as their default values.
+     *         otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
+     *         length {@link #getNumOtherOrdinals()}.
+     *         Returns null, if ecrs is null or empty.
      */
-    public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
+        if (ecrs == null || ecrs.length == 0) {
             return null;
         }
-        final SparseLongArray delta = new SparseLongArray();
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int updatedSubsystem = energyArray.getSubsystem(i);
-            final long newEnergyUJ = energyArray.getEnergy(i);
-            final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem];
+        final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
 
-            // If this is the first valid energy, there is no delta to take.
-            if (oldEnergyUJ < 0) continue;
-            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
-            if (deltaUJ == 0) continue;
-            if (deltaUJ < 0) {
-                Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ
-                        + ") is less than old energy (" + oldEnergyUJ + "). Skipping. ");
+        for (final EnergyConsumerResult ecr : ecrs) {
+            // Extract the new energy data for the current consumer.
+            final int consumerId = ecr.id;
+            final long newEnergyUJ = ecr.energyUWs;
+            final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
+
+            // Look up the static information about this consumer.
+            final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
+            if (consumer == null) {
+                Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
                 continue;
             }
-            delta.put(updatedSubsystem, deltaUJ);
+            final int type = consumer.type;
+            final int ordinal = consumer.ordinal;
+
+            // Look up, and update, the old energy information about this consumer.
+            final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+            mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+            final SparseLongArray otherUidEnergies
+                    = updateAndGetDeltaForTypeOther(consumer, newAttributions);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+
+            // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
+            // there's no attribution either. Technically that isn't enforced at the HAL, but we
+            // can't really trust data like that anyway.
+
+            if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+                        + ") < old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+
+            switch (type) {
+                case EnergyConsumerType.DISPLAY:
+                    output.displayEnergyUJ = deltaUJ;
+                    break;
+                case EnergyConsumerType.OTHER:
+                    if (output.otherTotalEnergyUJ == null) {
+                        output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
+                        output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
+                    }
+                    output.otherTotalEnergyUJ[ordinal] = deltaUJ;
+                    output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
+                    break;
+                default:
+                    Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
+
+            }
         }
-
-        fillGivenSubsystems(energyArray);
-
-        return delta;
+        return output;
     }
 
     /**
-     * Check if a subsystem's measured energy is available.
-     * @param subsystem which subsystem.
-     * @return true if subsystem is available.
+     * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
+     * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
+     * difference (delta) between the previously stored values and the passed-in values.
+     *
+     * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
+     * @param newAttributions Record of uids and their new energyUJ values.
+     *                        Any uid not present is treated as having energy 0.
+     *                        If null or empty, all uids are treated as having energy 0.
+     * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
+     *         consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
+     *         Returns null if no delta available to calculate.
      */
-    public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) {
-        return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE;
+    private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
+            @NonNull EnergyConsumer consumerInfo,
+            @Nullable EnergyConsumerAttribution[] newAttributions) {
+
+        if (consumerInfo.type != EnergyConsumerType.OTHER) {
+            return null;
+        }
+        if (newAttributions == null) {
+            // Treat null as empty (i.e. all uids have 0 energy).
+            newAttributions = new EnergyConsumerAttribution[0];
+        }
+
+        // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
+        SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
+
+        // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
+        if (uidOldEnergyMap == null) {
+            uidOldEnergyMap = new SparseLongArray(newAttributions.length);
+            mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
+            for (EnergyConsumerAttribution newAttribution : newAttributions) {
+                uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
+            }
+            return null;
+        }
+
+        // Map uid -> energyDelta. No initial capacity since many deltas might be 0.
+        final SparseLongArray uidEnergyDeltas = new SparseLongArray();
+
+        for (EnergyConsumerAttribution newAttribution : newAttributions) {
+            final int uid = newAttribution.uid;
+            final long newEnergyUJ = newAttribution.energyUWs;
+            // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
+            final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
+            uidOldEnergyMap.put(uid, newEnergyUJ);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+            if (oldEnergyUJ < 0) continue;
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+                        + ") but old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+            uidEnergyDeltas.put(uid, deltaUJ);
+        }
+        return uidEnergyDeltas;
     }
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Measured energy snapshot (microjoules):");
-        pw.print("   ");
-        for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) {
-            final long energyUJ = mMeasuredEnergySnapshots[i];
-            if (energyUJ == UNAVAILABLE) continue;
-            pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]);
-            pw.print(" : ");
-            pw.print(energyUJ);
-            if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) {
-                pw.print(", ");
-            }
+        pw.println("Measured energy snapshot");
+        pw.println("List of EnergyConsumers:");
+        for (int i = 0; i < mEnergyConsumers.size(); i++) {
+            final int id = mEnergyConsumers.keyAt(i);
+            final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
+            pw.println(String.format("    Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
+                    consumer.id, consumer.ordinal, consumer.type, consumer.name));
         }
+        pw.println("Map of consumerIds to energy (in microjoules):");
+        for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
+            final int id = mMeasuredEnergySnapshots.keyAt(i);
+            final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
+            pw.println(String.format("    Consumer %d has energy %d uJ}", id, energyUJ));
+        }
+        pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
+        pw.println("    " + mAttributionSnapshots);
         pw.println();
     }
+
+    /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) return 0;
+        int numOtherOrdinals = 0;
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
+        }
+        return numOtherOrdinals;
+    }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3acad49..76c3467 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -16,17 +16,23 @@
 
 package com.android.server.app;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.ActivityManager;
 import android.app.GameManager;
 import android.app.GameManager.GameMode;
 import android.app.IGameManagerService;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,6 +87,14 @@
             switch (msg.what) {
                 case WRITE_SETTINGS: {
                     final int userId = (int) msg.obj;
+                    if (userId < 0) {
+                        Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
+                        synchronized (mLock) {
+                            removeMessages(WRITE_SETTINGS, msg.obj);
+                        }
+                        break;
+                    }
+
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mLock) {
                         removeMessages(WRITE_SETTINGS, msg.obj);
@@ -94,6 +108,15 @@
                 }
                 case REMOVE_SETTINGS: {
                     final int userId = (int) msg.obj;
+                    if (userId < 0) {
+                        Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
+                        synchronized (mLock) {
+                            removeMessages(WRITE_SETTINGS, msg.obj);
+                            removeMessages(REMOVE_SETTINGS, msg.obj);
+                        }
+                        break;
+                    }
+
                     synchronized (mLock) {
                         // Since the user was removed, ignore previous write message
                         // and do write here.
@@ -146,9 +169,23 @@
         }
     }
 
-    //TODO(b/178111358) Add proper permission check and multi-user handling
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public @GameMode int getGameMode(String packageName, int userId) {
+        if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) {
+            Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE"));
+            return GameManager.GAME_MODE_UNSUPPORTED;
+        }
+
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "getGameMode",
+                "com.android.server.app.GameManagerService");
+
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
                 return GameManager.GAME_MODE_UNSUPPORTED;
@@ -158,9 +195,18 @@
         }
     }
 
-    //TODO(b/178111358) Add proper permission check and multi-user handling
     @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
+        if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) {
+            Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE"));
+            return;
+        }
+
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "setGameMode",
+                "com.android.server.app.GameManagerService");
+
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
                 return;
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 14292d9c..c956043 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -686,7 +686,8 @@
      * @return true if this AuthSession is finished, e.g. should be set to null
      */
     boolean onCancelAuthSession(boolean force) {
-        final boolean authStarted = mState == STATE_AUTH_STARTED
+        final boolean authStarted = mState == STATE_AUTH_CALLED
+                || mState == STATE_AUTH_STARTED
                 || mState == STATE_AUTH_STARTED_UI_SHOWING;
 
         if (authStarted && !force) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 0536e78..b31a54b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -311,4 +311,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_AUTHENTICATE;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 8fa3bbb..81ce2d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -82,12 +82,18 @@
     @NonNull protected Callback mCallback;
 
     /**
-     * Returns a ClientMonitorEnum constant defined in biometrics.proto
-     * @return
+     * @return A ClientMonitorEnum constant defined in biometrics.proto
      */
     public abstract int getProtoEnum();
 
     /**
+     * @return True if the ClientMonitor should cancel any current and pending interruptable clients
+     */
+    public boolean interruptsPrecedingClients() {
+        return false;
+    }
+
+    /**
      * @param context    system_server context
      * @param token      a unique token for the client
      * @param listener   recipient of related events (e.g. authentication)
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c5237ab..20c25c3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -475,14 +475,17 @@
      */
     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
             @Nullable BaseClientMonitor.Callback clientCallback) {
-        // Mark any interruptable pending clients as canceling. Once they reach the head of the
-        // queue, the scheduler will send ERROR_CANCELED and skip the operation.
-        for (Operation operation : mPendingOperations) {
-            if (operation.mClientMonitor instanceof Interruptable
-                    && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
-                Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
-                        + operation.mClientMonitor);
-                operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+        // If the incoming operation should interrupt preceding clients, mark any interruptable
+        // pending clients as canceling. Once they reach the head of the queue, the scheduler will
+        // send ERROR_CANCELED and skip the operation.
+        if (clientMonitor.interruptsPrecedingClients()) {
+            for (Operation operation : mPendingOperations) {
+                if (operation.mClientMonitor instanceof Interruptable
+                        && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+                    Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+                            + operation.mClientMonitor);
+                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+                }
             }
         }
 
@@ -490,8 +493,11 @@
         Slog.d(getTag(), "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
-        // If the current operation is cancellable, start the cancellation process.
-        if (mCurrentOperation != null && mCurrentOperation.mClientMonitor instanceof Interruptable
+        // If the new operation should interrupt preceding clients, and if the current operation is
+        // cancellable, start the cancellation process.
+        if (clientMonitor.interruptsPrecedingClients()
+                && mCurrentOperation != null
+                && mCurrentOperation.mClientMonitor instanceof Interruptable
                 && mCurrentOperation.mState == Operation.STATE_STARTED) {
             Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
             cancelInternal(mCurrentOperation);
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index da76af8..25b7add 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -16,9 +16,12 @@
 
 package com.android.server.biometrics.sensors;
 
+import android.annotation.NonNull;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -174,4 +177,33 @@
             mFingerprintServiceReceiver.onUdfpsPointerUp(sensorId);
         }
     }
+
+    // Face-specific callbacks for FaceManager only
+
+    /**
+     * Called each time a new frame is received during face authentication.
+     *
+     * @param frame Information about the current frame.
+     *
+     * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
+     */
+    public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame)
+            throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onAuthenticationFrame(frame);
+        }
+    }
+
+    /**
+     * Called each time a new frame is received during face enrollment.
+     *
+     * @param frame Information about the current frame.
+     *
+     * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
+     */
+    public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onEnrollmentFrame(frame);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 8d81016..e1320d8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -113,4 +113,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_ENROLL;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index ce24e5e..de57186 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -69,6 +69,8 @@
             final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
                     ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
 
+            Slog.d(TAG, "Enumerate onClientFinished: " + clientMonitor + ", success: " + success);
+
             if (!unknownHALTemplates.isEmpty()) {
                 Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
             }
@@ -90,6 +92,7 @@
     private final Callback mRemoveCallback = new Callback() {
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+            Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success);
             mCallback.onClientFinished(InternalCleanupClient.this, success);
         }
     };
@@ -115,6 +118,8 @@
     }
 
     private void startCleanupUnknownHalTemplates() {
+        Slog.d(TAG, "startCleanupUnknownHalTemplates, size: " + mUnknownHALTemplates.size());
+
         UserTemplate template = mUnknownHALTemplates.get(0);
         mUnknownHALTemplates.remove(template);
         mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
@@ -138,6 +143,8 @@
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
                 getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+
+        Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
         mCurrentTask.start(mEnumerateCallback);
     }
 
@@ -165,6 +172,7 @@
                     + mCurrentTask.getClass().getSimpleName());
             return;
         }
+        Slog.d(TAG, "onEnumerated, remaining: " + remaining);
         ((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 9d19fdf..e3feb74 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -82,6 +82,7 @@
 
     private void handleEnumeratedTemplate(BiometricAuthenticator.Identifier identifier) {
         if (identifier == null) {
+            Slog.d(TAG, "Null identifier");
             return;
         }
         Slog.v(TAG, "handleEnumeratedTemplate: " + identifier.getBiometricId());
@@ -103,6 +104,7 @@
 
     private void doTemplateCleanup() {
         if (mEnrolledList == null) {
+            Slog.d(TAG, "Null enrolledList");
             return;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index d2673d2..897ebd7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -24,6 +24,8 @@
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.util.Slog;
@@ -117,6 +119,16 @@
         public void onChallengeInterruptFinished(int sensorId) {
 
         }
+
+        @Override
+        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+        }
+
+        @Override
+        public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+        }
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
@@ -183,7 +195,7 @@
         mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
     }
 
-    // TODO(b/174619156): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
+    // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
@@ -194,7 +206,7 @@
         AuthenticationFrame authenticationFrame = new AuthenticationFrame();
         authenticationFrame.data = data;
 
-        // TODO(b/174619156): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
+        // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
         // This will need to call the correct callback once the onAcquired callback is removed.
         mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFrame(
                 authenticationFrame);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 3057766..8f55402 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.FaceAuthenticationFrame;
-import android.hardware.face.FaceDataFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -179,19 +178,16 @@
         return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
     }
 
-    private boolean shouldSend(int acquireInfo, int vendorCode) {
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
-            return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
-        } else {
-            return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
-        }
+    private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
+        return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
+                ? !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode)
+                : !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
     }
 
     @Override
     public void onAcquired(int acquireInfo, int vendorCode) {
         mLastAcquire = acquireInfo;
-
-        final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
 
@@ -201,9 +197,21 @@
      * @param frame Information about the current frame.
      */
     public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame) {
-        // TODO(b/178414967): Send additional frame data to the client callback.
-        final FaceDataFrame data = frame.getData();
-        onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+        // Log acquisition but don't send it to the client yet, since that's handled below.
+        final int acquireInfo = frame.getData().getAcquiredInfo();
+        final int vendorCode = frame.getData().getVendorCode();
+        mLastAcquire = acquireInfo;
+        onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
+
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
+        if (shouldSend && getListener() != null) {
+            try {
+                getListener().onAuthenticationFrame(frame);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to send authentication frame", e);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }
     }
 
     @Override public void onLockoutTimed(long durationMillis) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index da657b9..898d81b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -27,7 +27,6 @@
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
-import android.hardware.face.FaceDataFrame;
 import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -101,14 +100,15 @@
                 getTargetUserId()).size() >= mMaxTemplatesPerUser;
     }
 
+    private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
+        return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
+                ? !Utils.listContains(mEnrollIgnoreListVendor, vendorCode)
+                : !Utils.listContains(mEnrollIgnoreList, acquireInfo);
+    }
+
     @Override
     public void onAcquired(int acquireInfo, int vendorCode) {
-        final boolean shouldSend;
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
-            shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
-        } else {
-            shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
-        }
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
 
@@ -118,9 +118,20 @@
      * @param frame Information about the current frame.
      */
     public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) {
-        // TODO(b/178414967): Send additional frame data to the client callback.
-        final FaceDataFrame data = frame.getData();
-        onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+        // Log acquisition but don't send it to the client yet, since that's handled below.
+        final int acquireInfo = frame.getData().getAcquiredInfo();
+        final int vendorCode = frame.getData().getVendorCode();
+        onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
+
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
+        if (shouldSend && getListener() != null) {
+            try {
+                getListener().onEnrollmentFrame(frame);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to send enrollment frame", e);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index baeb3fd..81e90df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -496,6 +496,7 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        Slog.w(mTag, "setTestHalEnabled: " + enabled);
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 1d57073..a38da3a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -23,86 +23,85 @@
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.common.NativeHandle;
 import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only no-ops.
  */
 public class TestHal extends IFace.Stub {
+    private static final String TAG = "face.aidl.TestHal";
     @Override
     public SensorProps[] getSensorProps() {
+        Slog.w(TAG, "getSensorProps");
         return new SensorProps[0];
     }
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
-        return new ISession() {
+        return new ISession.Stub() {
             @Override
             public void generateChallenge(int cookie, int timeoutSec) {
-
+                Slog.w(TAG, "generateChallenge, cookie: " + cookie);
             }
 
             @Override
             public void revokeChallenge(int cookie, long challenge) {
-
+                Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
                     byte enrollmentType, byte[] features, NativeHandle previewSurface) {
+                Slog.w(TAG, "enroll, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
+                Slog.w(TAG, "authenticate, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
+                Slog.w(TAG, "detectInteraction, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public void enumerateEnrollments(int cookie) {
-
+                Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+                Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void getFeatures(int cookie, int enrollmentId) {
-
+                Slog.w(TAG, "getFeatures, cookie: " + cookie);
             }
 
             @Override
             public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
                     byte feature, boolean enabled) {
-
+                Slog.w(TAG, "setFeature, cookie: " + cookie);
             }
 
             @Override
             public void getAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void invalidateAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void resetLockout(int cookie, HardwareAuthToken hat) {
-
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
+                Slog.w(TAG, "resetLockout, cookie: " + cookie);
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 4142a52..d519d60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -22,6 +22,8 @@
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.util.Slog;
@@ -106,6 +108,16 @@
         public void onChallengeInterruptFinished(int sensorId) {
 
         }
+
+        @Override
+        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+        }
+
+        @Override
+        public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+        }
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId, @NonNull Face10 face10,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index bab1114d..00ca802 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -25,10 +25,12 @@
 import android.hardware.biometrics.face.V1_1.IBiometricsFace;
 import android.os.NativeHandle;
 import android.os.RemoteException;
+import android.util.Slog;
 
 import java.util.ArrayList;
 
 public class TestHal extends IBiometricsFace.Stub {
+    private static final String TAG = "face.hidl.TestHal";
     @Nullable
     private IBiometricsFaceClientCallback mCallback;
 
@@ -47,6 +49,7 @@
 
     @Override
     public OptionalUint64 generateChallenge(int challengeTimeoutSec) {
+        Slog.w(TAG, "generateChallenge");
         final OptionalUint64 result = new OptionalUint64();
         result.status = Status.OK;
         result.value = 0;
@@ -55,6 +58,7 @@
 
     @Override
     public int enroll(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures) {
+        Slog.w(TAG, "enroll");
         return 0;
     }
 
@@ -95,16 +99,19 @@
 
     @Override
     public int enumerate() {
+        Slog.w(TAG, "enumerate");
         return 0;
     }
 
     @Override
     public int remove(int faceId) {
+        Slog.w(TAG, "remove");
         return 0;
     }
 
     @Override
     public int authenticate(long operationId) {
+        Slog.w(TAG, "authenticate");
         return 0;
     }
 
@@ -115,6 +122,7 @@
 
     @Override
     public int resetLockout(ArrayList<Byte> hat) {
+        Slog.w(TAG, "resetLockout");
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 9611192..bcd1b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -98,4 +98,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_DETECT_INTERACTION;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 7e4ee9e7..73b59cf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -476,6 +476,7 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        Slog.w(mTag, "setTestHalEnabled, enabled");
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index a31bcdc..66b68ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -22,89 +22,89 @@
 import android.hardware.biometrics.fingerprint.ISessionCallback;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only provides no-ops.
  */
 public class TestHal extends IFingerprint.Stub {
+    private static final String TAG = "fingerprint.aidl.TestHal";
+
     @Override
     public SensorProps[] getSensorProps() {
+        Slog.w(TAG, "getSensorProps");
         return new SensorProps[0];
     }
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
-        return new ISession() {
+        return new ISession.Stub() {
             @Override
             public void generateChallenge(int cookie, int timeoutSec) {
-
+                Slog.w(TAG, "generateChallenge, cookie: " + cookie);
             }
 
             @Override
             public void revokeChallenge(int cookie, long challenge) {
-
+                Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+                Slog.w(TAG, "enroll, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
+                Slog.w(TAG, "authenticate, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
+                Slog.w(TAG, "detectInteraction, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public void enumerateEnrollments(int cookie) {
-
+                Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+                Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void getAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void invalidateAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void resetLockout(int cookie, HardwareAuthToken hat) {
-
+                Slog.w(TAG, "resetLockout, cookie: " + cookie);
             }
 
             @Override
             public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
+                Slog.w(TAG, "onPointerDown");
             }
 
             @Override
             public void onPointerUp(int pointerId) {
-
+                Slog.w(TAG, "onPointerUp");
             }
 
             @Override
             public void onUiReady() {
-
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
+                Slog.w(TAG, "onUiReady");
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 7989dca..8acb284 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -130,4 +130,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_DETECT_INTERACTION;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index b7aec0e..57447f3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -21,11 +21,14 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
 import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
 import android.os.RemoteException;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only provides no-ops.
  */
 public class TestHal extends IBiometricsFingerprint.Stub {
+    private static final String TAG = "fingerprint.hidl.TestHal";
+
     @Nullable
     private IBiometricsFingerprintClientCallback mCallback;
 
@@ -57,6 +60,7 @@
 
     @Override
     public int enroll(byte[] hat, int gid, int timeoutSec) {
+        Slog.w(TAG, "enroll");
         return 0;
     }
 
@@ -80,11 +84,13 @@
 
     @Override
     public int enumerate() {
+        Slog.w(TAG, "Enumerate");
         return 0;
     }
 
     @Override
     public int remove(int gid, int fid) {
+        Slog.w(TAG, "Remove");
         return 0;
     }
 
@@ -95,6 +101,7 @@
 
     @Override
     public int authenticate(long operationId, int gid) {
+        Slog.w(TAG, "Authenticate");
         return 0;
     }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index bff1a5c..c05e253 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -717,8 +717,9 @@
                 mNumBackgroundNetworkRequests += delta;
                 break;
 
-            case TRACK_DEFAULT:
             case LISTEN:
+            case TRACK_DEFAULT:
+            case TRACK_SYSTEM_DEFAULT:
                 break;
 
             case NONE:
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 3d71b0a..6f112d7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -161,13 +161,20 @@
         if (nai != null) {
             transportType = approximateTransportType(nai);
             final String extraInfo = nai.networkInfo.getExtraInfo();
-            name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo;
+            if (nai.linkProperties != null && nai.linkProperties.getCaptivePortalData() != null
+                    && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
+                    .getVenueFriendlyName())) {
+                name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+            } else {
+                name = TextUtils.isEmpty(extraInfo)
+                        ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+            }
             // Only notify for Internet-capable networks.
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
         } else {
             // Legacy notifications.
             transportType = TRANSPORT_CELLULAR;
-            name = null;
+            name = "";
         }
 
         // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
@@ -193,35 +200,30 @@
         final CharSequence details;
         int icon = getIcon(transportType);
         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.wifi_no_internet,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
         } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
             if (transportType == TRANSPORT_CELLULAR) {
                 title = r.getString(R.string.mobile_no_internet);
             } else if (transportType == TRANSPORT_WIFI) {
-                title = r.getString(R.string.wifi_no_internet,
-                        WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+                title = r.getString(R.string.wifi_no_internet, name);
             } else {
                 title = r.getString(R.string.other_networks_no_internet);
             }
             details = r.getString(R.string.private_dns_broken_detailed);
         } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
                 && transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.network_partial_connectivity,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.network_partial_connectivity, name);
             details = r.getString(R.string.network_partial_connectivity_detailed);
         } else if (notifyType == NotificationType.LOST_INTERNET &&
                 transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.wifi_no_internet,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
         } else if (notifyType == NotificationType.SIGN_IN) {
             switch (transportType) {
                 case TRANSPORT_WIFI:
                     title = r.getString(R.string.wifi_available_sign_in, 0);
-                    details = r.getString(R.string.network_available_sign_in_detailed,
-                            WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+                    details = r.getString(R.string.network_available_sign_in_detailed, name);
                     break;
                 case TRANSPORT_CELLULAR:
                     title = r.getString(R.string.network_available_sign_in, 0);
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index b5f20d7..c480594 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -41,7 +41,6 @@
 import android.os.MessageQueue;
 import android.os.Messenger;
 import android.system.ErrnoException;
-import android.system.Int32Ref;
 import android.system.Os;
 import android.util.Log;
 import android.util.SparseArray;
@@ -306,9 +305,8 @@
 
     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        Int32Ref result = new Int32Ref(-1);
-        Os.ioctlInt(fd, SIOCINQ, result);
-        if (result.value != 0) {
+        final int result = Os.ioctlInt(fd, SIOCINQ);
+        if (result != 0) {
             Log.e(TAG, "Read queue has data");
             return false;
         }
@@ -317,9 +315,8 @@
 
     private static boolean isSendQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        Int32Ref result = new Int32Ref(-1);
-        Os.ioctlInt(fd, SIOCOUTQ, result);
-        if (result.value != 0) {
+        final int result = Os.ioctlInt(fd, SIOCOUTQ);
+        if (result != 0) {
             Log.e(TAG, "Write queue has data");
             return false;
         }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index fc2c7e0..33c19b1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -74,6 +74,7 @@
 import android.net.UnderlyingNetworkInfo;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
 import android.net.ipsec.ike.ChildSessionParams;
@@ -435,6 +436,7 @@
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
 
         loadAlwaysOnPackage(keyStore);
     }
@@ -929,6 +931,7 @@
                 jniReset(mInterface);
                 mInterface = null;
                 mNetworkCapabilities.setUids(null);
+                mNetworkCapabilities.setTransportInfo(null);
             }
 
             // Revoke the connection or stop the VpnRunner.
@@ -999,6 +1002,8 @@
                 case VpnManager.TYPE_VPN_SERVICE:
                     toChange = new String[] {AppOpsManager.OPSTR_ACTIVATE_VPN};
                     break;
+                case VpnManager.TYPE_VPN_LEGACY:
+                    return false;
                 default:
                     Log.wtf(TAG, "Unrecognized VPN type while granting authorization");
                     return false;
@@ -1029,6 +1034,8 @@
                 return isVpnServicePreConsented(context, packageName);
             case VpnManager.TYPE_VPN_PLATFORM:
                 return isVpnProfilePreConsented(context, packageName);
+            case VpnManager.TYPE_VPN_LEGACY:
+                return VpnConfig.LEGACY_VPN.equals(packageName);
             default:
                 return false;
         }
@@ -1211,6 +1218,8 @@
         mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId,
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
+        mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
+
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
         if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) {
@@ -1736,6 +1745,7 @@
     private void cleanupVpnStateLocked() {
         mStatusIntent = null;
         mNetworkCapabilities.setUids(null);
+        mNetworkCapabilities.setTransportInfo(null);
         mConfig = null;
         mInterface = null;
 
@@ -1846,22 +1856,18 @@
     }
 
     /**
-     * Gets the currently running App-based VPN type
+     * Gets the currently running VPN type
      *
-     * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an
-     *     app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
+     * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running a
+     *     VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
      *     Settings-based, the Platform VPNs can be initiated by both apps and Settings.
      */
-    public synchronized int getActiveAppVpnType() {
-        if (VpnConfig.LEGACY_VPN.equals(mPackage)) {
-            return VpnManager.TYPE_VPN_NONE;
-        }
-
-        if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) {
-            return VpnManager.TYPE_VPN_PLATFORM;
-        } else {
-            return VpnManager.TYPE_VPN_SERVICE;
-        }
+    public synchronized int getActiveVpnType() {
+        if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE;
+        if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE;
+        return mVpnRunner instanceof IkeV2VpnRunner
+                ? VpnManager.TYPE_VPN_PLATFORM
+                : VpnManager.TYPE_VPN_LEGACY;
     }
 
     private void updateAlwaysOnNotification(DetailedState networkState) {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fa063b2..225da7a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -42,8 +42,8 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 4832e46..a62f67a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -28,8 +28,8 @@
 import android.util.Slog;
 import android.util.Spline;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d687221..1b25427 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -23,8 +23,8 @@
 import android.util.Slog;
 import android.view.DisplayAddress;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index bf16a6d..501533d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,7 +26,7 @@
 import android.view.RoundedCorners;
 import android.view.Surface;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.util.Arrays;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..dce6375 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -106,9 +106,9 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.AnimationThread;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2df33652..9320f50 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -53,8 +53,8 @@
 import android.util.TimeUtils;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 173adce..1d20d87 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,7 +26,7 @@
 import android.view.Choreographer;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 73ebb2e..3b66236 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,7 +38,7 @@
 import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 7916d81..26004a8 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,7 +20,7 @@
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 /**
  * A custom animator that progressively updates a property value at
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
index 017f11c..d514aab 100644
--- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -17,6 +17,8 @@
 package com.android.server.graphics.fonts;
 
 import android.annotation.NonNull;
+import android.graphics.FontListParser;
+import android.text.FontConfig;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -30,6 +32,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 /* package */ class PersistentSystemFontConfig {
@@ -38,11 +42,13 @@
     private static final String TAG_ROOT = "fontConfig";
     private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
     private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
+    private static final String TAG_FAMILY = "family";
     private static final String ATTR_VALUE = "value";
 
     /* package */ static class Config {
         public long lastModifiedDate;
         public final Set<String> updatedFontDirs = new ArraySet<>();
+        public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>();
     }
 
     /**
@@ -72,6 +78,11 @@
                     case TAG_UPDATED_FONT_DIR:
                         out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
                         break;
+                    case TAG_FAMILY:
+                        // updatableFontMap is not ready here. We get the base file names by passing
+                        // empty fontDir, and resolve font paths later.
+                        out.fontFamilies.add(FontListParser.readFamily(
+                                parser, "" /* fontDir */, null /* updatableFontMap */));
                     default:
                         Slog.w(TAG, "Skipping unknown tag: " + tag);
                 }
@@ -97,6 +108,13 @@
             out.attribute(null, ATTR_VALUE, dir);
             out.endTag(null, TAG_UPDATED_FONT_DIR);
         }
+        List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
+        for (int i = 0; i < fontFamilies.size(); i++) {
+            FontConfig.FontFamily fontFamily = fontFamilies.get(i);
+            out.startTag(null, TAG_FAMILY);
+            FontListParser.writeFamily(out, fontFamily);
+            out.endTag(null, TAG_FAMILY);
+        }
         out.endTag(null, TAG_ROOT);
 
         out.endDocument();
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index dac94f6..45f2a38 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -31,6 +31,8 @@
 import android.util.Base64;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
@@ -40,6 +42,7 @@
 import java.io.IOException;
 import java.security.SecureRandom;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -118,6 +121,12 @@
      */
     private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
 
+    /**
+     * A mutable map containing mapping from font family name to {@link FontConfig.FontFamily}.
+     * The FontFamily entries only reference font files in {@link #mFontFileInfoMap}.
+     */
+    private final ArrayMap<String, FontConfig.FontFamily> mFontFamilyMap = new ArrayMap<>();
+
     UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
             FsverityUtil fsverityUtil) {
         this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE));
@@ -136,6 +145,7 @@
 
     /* package */ void loadFontFileMap() {
         mFontFileInfoMap.clear();
+        mFontFamilyMap.clear();
         mLastModifiedDate = 0;
         boolean success = false;
         try {
@@ -168,6 +178,13 @@
                 FontFileInfo fontFileInfo = validateFontFile(files[0]);
                 addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */);
             }
+            // Resolve font file paths.
+            List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
+            for (int i = 0; i < fontFamilies.size(); i++) {
+                FontConfig.FontFamily fontFamily = fontFamilies.get(i);
+                // Ignore failures as updated fonts may be obsoleted by system OTA update.
+                addFontFamily(fontFamily);
+            }
             success = true;
         } catch (Throwable t) {
             // If something happened during loading system fonts, clear all contents in finally
@@ -177,6 +194,7 @@
             // Delete all files just in case if we find a problematic file.
             if (!success) {
                 mFontFileInfoMap.clear();
+                mFontFamilyMap.clear();
                 mLastModifiedDate = 0;
                 FileUtils.deleteContents(mFilesDir);
             }
@@ -186,10 +204,11 @@
     /* package */ void clearUpdates() throws SystemFontException {
         mFontFileInfoMap.clear();
         FileUtils.deleteContents(mFilesDir);
+        mFontFamilyMap.clear();
 
         mLastModifiedDate = Instant.now().getEpochSecond();
         try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
-            PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+            PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
         } catch (Exception e) {
             throw new SystemFontException(
                     FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -206,17 +225,29 @@
     public void update(List<FontUpdateRequest> requests) throws SystemFontException {
         // Backup the mapping for rollback.
         ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
+        ArrayMap<String, FontConfig.FontFamily> backupFamilies = new ArrayMap<>(mFontFamilyMap);
         long backupLastModifiedDate = mLastModifiedDate;
         boolean success = false;
         try {
             for (FontUpdateRequest request : requests) {
-                installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
+                switch (request.getType()) {
+                    case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
+                        installFontFile(
+                                request.getFd().getFileDescriptor(), request.getSignature());
+                        break;
+                    case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
+                        // TODO: define error code.
+                        if (!addFontFamily(request.getFontFamily())) {
+                            throw new IllegalArgumentException("Invalid font family");
+                        }
+                        break;
+                }
             }
 
             // Write config file.
             mLastModifiedDate = Instant.now().getEpochSecond();
             try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
-                PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+                PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
             } catch (Exception e) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -234,6 +265,8 @@
             if (!success) {
                 mFontFileInfoMap.clear();
                 mFontFileInfoMap.putAll(backupMap);
+                mFontFamilyMap.clear();
+                mFontFamilyMap.putAll(backupFamilies);
                 mLastModifiedDate = backupLastModifiedDate;
             }
         }
@@ -454,12 +487,52 @@
         }
     }
 
-    private PersistentSystemFontConfig.Config getPersistentConfig() {
+    /**
+     * Adds a font family to {@link #mFontFamilyMap} and returns true on success.
+     *
+     * <p>This method only accepts adding or updating a font family with a name.
+     * This is to prevent bad font family update from removing glyphs from font fallback chains.
+     * Unnamed font families are used as other named font family's fallback fonts to guarantee a
+     * complete glyph coverage.
+     */
+    private boolean addFontFamily(FontConfig.FontFamily fontFamily) {
+        if (fontFamily.getName() == null) {
+            Slog.e(TAG, "Name is null.");
+            return false;
+        }
+        FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
+        if (resolvedFontFamily == null) {
+            Slog.e(TAG, "Required fonts are not available");
+            return false;
+        }
+        mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
+        return true;
+    }
+
+    @Nullable
+    private FontConfig.FontFamily resolveFontFiles(FontConfig.FontFamily fontFamily) {
+        List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontFamily.getFontList().size());
+        List<FontConfig.Font> fontList = fontFamily.getFontList();
+        for (int i = 0; i < fontList.size(); i++) {
+            FontConfig.Font font = fontList.get(i);
+            FontFileInfo info = mFontFileInfoMap.get(font.getFile().getName());
+            if (info == null) {
+                return null;
+            }
+            resolvedFonts.add(new FontConfig.Font(info.mFile, null, font.getStyle(),
+                    font.getTtcIndex(), font.getFontVariationSettings(), font.getFontFamilyName()));
+        }
+        return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
+                fontFamily.getLocaleList(), fontFamily.getVariant());
+    }
+
+    private PersistentSystemFontConfig.Config createPersistentConfig() {
         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
         config.lastModifiedDate = mLastModifiedDate;
         for (FontFileInfo info : mFontFileInfoMap.values()) {
             config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
         }
+        config.fontFamilies.addAll(mFontFamilyMap.values());
         return config;
     }
 
@@ -471,8 +544,24 @@
         return map;
     }
 
+    @VisibleForTesting
+    Map<String, FontConfig.FontFamily> getFontFamilyMap() {
+        return mFontFamilyMap;
+    }
+
     /* package */ FontConfig getSystemFontConfig() {
-        return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
+        FontConfig config = SystemFonts.getSystemFontConfig(getFontFileMap(), 0, 0);
+        List<FontConfig.FontFamily> mergedFamilies =
+                new ArrayList<>(config.getFontFamilies().size() + mFontFamilyMap.size());
+        // We should keep the first font family (config.getFontFamilies().get(0)) because it's used
+        // as a fallback font. See SystemFonts.java.
+        mergedFamilies.addAll(config.getFontFamilies());
+        // When building Typeface, a latter font family definition will override the previous font
+        // family definition with the same name. An exception is config.getFontFamilies.get(0),
+        // which will be used as a fallback font without being overridden.
+        mergedFamilies.addAll(mFontFamilyMap.values());
+        return new FontConfig(
+                mergedFamilies, config.getAliases(), mLastModifiedDate, mConfigVersion);
     }
 
     /* package */ int getConfigVersion() {
diff --git a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
index 1a0a639..7b646b3 100644
--- a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
@@ -17,6 +17,7 @@
  */
 
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.DisplayStatusCallback;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -65,6 +66,20 @@
 
     @Override
     boolean start() {
+        HdmiControlService service = localDevice().mService;
+        if (service.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+            HdmiDeviceInfo deviceInfo = service.getHdmiCecNetwork().getCecDeviceInfo(
+                    mTargetAddress);
+            if (deviceInfo != null
+                    && deviceInfo.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                int powerStatus = deviceInfo.getDevicePowerStatus();
+                if (powerStatus != HdmiControlManager.POWER_STATUS_UNKNOWN) {
+                    invokeCallback(powerStatus);
+                    finish();
+                    return true;
+                }
+            }
+        }
         queryDevicePowerStatus();
         mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 75b52f9..2995252 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -239,6 +239,7 @@
     @ServiceThreadOnly
     protected void onActiveSourceLost() {
         assertRunOnServiceThread();
+        mService.pauseActiveMediaSessions();
         switch (mService.getHdmiCecConfig().getStringValue(
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
             case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index bdf92ca..fa1fb48 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -56,6 +56,8 @@
 import android.hardware.tv.cec.V1_0.OptionKey;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputManager.TvInputCallback;
 import android.net.Uri;
@@ -3294,6 +3296,16 @@
         }
     }
 
+    @VisibleForTesting
+    void pauseActiveMediaSessions() {
+        MediaSessionManager mediaSessionManager = getContext()
+                .getSystemService(MediaSessionManager.class);
+        List<MediaController> mediaControllers = mediaSessionManager.getActiveSessions(null);
+        for (MediaController mediaController : mediaControllers) {
+            mediaController.getTransportControls().pause();
+        }
+    }
+
     void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
         synchronized (mLock) {
             mActiveSource.logicalAddress = logicalAddress;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a4c8b7..00ae30e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5246,12 +5246,7 @@
                     imeTracing.startTrace(null);
                 });
             }
-        }
-        doDump(fd, pw, args, asProto);
-    }
 
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
-        if (useProto) {
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
             dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
             proto.flush();
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 43c965d..42b0add 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -36,9 +36,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index ea759bf..47cb43e 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -79,6 +79,7 @@
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.stats.location.LocationStatsEnums;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 
@@ -87,6 +88,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.geofence.GeofenceManager;
 import com.android.server.location.geofence.GeofenceProxy;
 import com.android.server.location.gnss.GnssConfiguration;
@@ -98,7 +100,6 @@
 import com.android.server.location.injector.EmergencyHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationAttributionHelper;
-import com.android.server.location.injector.LocationEventLog;
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationUsageLogger;
@@ -147,9 +148,10 @@
 
         public Lifecycle(Context context) {
             super(context);
+            LocationEventLog eventLog = new LocationEventLog();
             mUserInfoHelper = new LifecycleUserInfoHelper(context);
-            mSystemInjector = new SystemInjector(context, mUserInfoHelper);
-            mService = new LocationManagerService(context, mSystemInjector);
+            mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
+            mService = new LocationManagerService(context, mSystemInjector, eventLog);
         }
 
         @Override
@@ -159,7 +161,7 @@
             // client caching behavior is only enabled after seeing the first invalidate
             LocationManager.invalidateLocalLocationEnabledCaches();
             // disable caching for our own process
-            Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class))
+            Objects.requireNonNull(getContext().getSystemService(LocationManager.class))
                     .disableLocalLocationEnabledCaches();
         }
 
@@ -221,6 +223,7 @@
 
     private final Context mContext;
     private final Injector mInjector;
+    private final LocationEventLog mEventLog;
     private final LocalService mLocalService;
 
     private final GeofenceManager mGeofenceManager;
@@ -245,10 +248,10 @@
     private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
             new CopyOnWriteArrayList<>();
 
-    LocationManagerService(Context context, Injector injector) {
+    LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
         mInjector = injector;
-
+        mEventLog = eventLog;
         mLocalService = new LocalService();
         LocalServices.addService(LocationManagerInternal.class, mLocalService);
 
@@ -256,7 +259,7 @@
 
         // set up passive provider first since it will be required for all other location providers,
         // which are loaded later once the system is ready.
-        mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
+        mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
         addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
 
         // TODO: load the gps provider here as well, which will require refactoring
@@ -297,7 +300,7 @@
             }
 
             LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
-                    providerName, mPassiveManager);
+                    mEventLog, providerName, mPassiveManager);
             addLocationProviderManager(manager, null);
             return manager;
         }
@@ -341,7 +344,7 @@
                 com.android.internal.R.string.config_networkLocationProviderPackageName);
         if (networkProvider != null) {
             LocationProviderManager networkManager = new LocationProviderManager(mContext,
-                    mInjector, NETWORK_PROVIDER, mPassiveManager);
+                    mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
             addLocationProviderManager(networkManager, networkProvider);
         } else {
             Log.w(TAG, "no network location provider found");
@@ -360,7 +363,7 @@
                 com.android.internal.R.string.config_fusedLocationProviderPackageName);
         if (fusedProvider != null) {
             LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
-                    FUSED_PROVIDER, mPassiveManager);
+                    mEventLog, FUSED_PROVIDER, mPassiveManager);
             addLocationProviderManager(fusedManager, fusedProvider);
         } else {
             Log.wtf(TAG, "no fused location provider found");
@@ -375,7 +378,7 @@
             mGnssManagerService.onSystemReady();
 
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
-                    GPS_PROVIDER, mPassiveManager);
+                    mEventLog, GPS_PROVIDER, mPassiveManager);
             addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
         }
 
@@ -431,7 +434,7 @@
             Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
         }
 
-        mInjector.getLocationEventLog().logLocationEnabled(userId, enabled);
+        mEventLog.logLocationEnabled(userId, enabled);
 
         Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
                 .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1193,9 +1196,27 @@
 
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
-        if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
-            mGnssManagerService.dump(fd, ipw, args);
-            return;
+        if (args.length > 0) {
+            LocationProviderManager manager = getLocationProviderManager(args[0]);
+            if (manager != null) {
+                ipw.println("Provider:");
+                ipw.increaseIndent();
+                manager.dump(fd, ipw, args);
+                ipw.decreaseIndent();
+
+                ipw.println("Event Log:");
+                ipw.increaseIndent();
+                mEventLog.iterate(manager.getName(), ipw::println);
+                ipw.decreaseIndent();
+                return;
+            }
+
+            if ("--gnssmetrics".equals(args[0])) {
+                if (mGnssManagerService != null) {
+                    mGnssManagerService.dump(fd, ipw, args);
+                }
+                return;
+            }
         }
 
         ipw.println("Location Manager State:");
@@ -1227,6 +1248,26 @@
         }
         ipw.decreaseIndent();
 
+        ipw.println("Historical Aggregate Location Provider Data:");
+        ipw.increaseIndent();
+        ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats =
+                mEventLog.copyAggregateStats();
+        for (int i = 0; i < aggregateStats.size(); i++) {
+            ipw.print(aggregateStats.keyAt(i));
+            ipw.println(":");
+            ipw.increaseIndent();
+            ArrayMap<String, LocationEventLog.AggregateStats> providerStats =
+                    aggregateStats.valueAt(i);
+            for (int j = 0; j < providerStats.size(); j++) {
+                ipw.print(providerStats.keyAt(j));
+                ipw.print(": ");
+                providerStats.valueAt(j).updateTotals();
+                ipw.println(providerStats.valueAt(j));
+            }
+            ipw.decreaseIndent();
+        }
+        ipw.decreaseIndent();
+
         if (mGnssManagerService != null) {
             ipw.println("GNSS Manager:");
             ipw.increaseIndent();
@@ -1241,7 +1282,7 @@
 
         ipw.println("Event Log:");
         ipw.increaseIndent();
-        mInjector.getLocationEventLog().iterate(ipw::println);
+        mEventLog.iterate(ipw::println);
         ipw.decreaseIndent();
     }
 
@@ -1320,7 +1361,6 @@
         private final Context mContext;
 
         private final UserInfoHelper mUserInfoHelper;
-        private final LocationEventLog mLocationEventLog;
         private final AlarmHelper mAlarmHelper;
         private final SystemAppOpsHelper mAppOpsHelper;
         private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
@@ -1339,19 +1379,17 @@
         @GuardedBy("this")
         private boolean mSystemReady;
 
-        SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+        SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
             mContext = context;
 
             mUserInfoHelper = userInfoHelper;
-            mLocationEventLog = new LocationEventLog();
             mAlarmHelper = new SystemAlarmHelper(context);
             mAppOpsHelper = new SystemAppOpsHelper(context);
             mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
                     mAppOpsHelper);
             mSettingsHelper = new SystemSettingsHelper(context);
             mAppForegroundHelper = new SystemAppForegroundHelper(context);
-            mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context,
-                    mLocationEventLog);
+            mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
             mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
             mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
             mLocationUsageLogger = new LocationUsageLogger();
@@ -1430,11 +1468,6 @@
         }
 
         @Override
-        public LocationEventLog getLocationEventLog() {
-            return mLocationEventLog;
-        }
-
-        @Override
         public LocationUsageLogger getLocationUsageLogger() {
             return mLocationUsageLogger;
         }
diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
index b5746bb..12dd3e6 100644
--- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
@@ -16,12 +16,13 @@
 
 package com.android.server.location.eventlog;
 
+import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.util.TimeUtils;
 
 import com.android.internal.util.Preconditions;
 
-import java.util.ListIterator;
+import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.function.Consumer;
 
@@ -35,6 +36,7 @@
         boolean isFiller();
         long getTimeDeltaMs();
         String getLogString();
+        boolean filter(@Nullable String filter);
     }
 
     private static final class FillerEvent implements Log {
@@ -62,6 +64,11 @@
         public String getLogString() {
             throw new AssertionError();
         }
+
+        @Override
+        public boolean filter(String filter) {
+            return false;
+        }
     }
 
     /**
@@ -87,6 +94,11 @@
         public final long getTimeDeltaMs() {
             return Integer.toUnsignedLong(mTimeDelta);
         }
+
+        @Override
+        public boolean filter(String filter) {
+            return false;
+        }
     }
 
     // circular buffer of log entries
@@ -198,6 +210,17 @@
         }
     }
 
+    /**
+     * Iterates over the event log, passing each filter-matching log string to the given
+     * consumer.
+     */
+    public synchronized void iterate(String filter, Consumer<String> consumer) {
+        LogIterator it = new LogIterator(filter);
+        while (it.hasNext()) {
+            consumer.accept(it.next());
+        }
+    }
+
     // returns the index of the first element
     private int startIndex() {
         return wrapIndex(mLogEndIndex - mLogSize);
@@ -205,12 +228,13 @@
 
     // returns the index after this one
     private int incrementIndex(int index) {
-        return wrapIndex(index + 1);
-    }
-
-    // returns the index before this one
-    private int decrementIndex(int index) {
-        return wrapIndex(index - 1);
+        if (index == -1) {
+            return startIndex();
+        } else if (index >= 0) {
+            return wrapIndex(index + 1);
+        } else {
+            throw new IllegalArgumentException();
+        }
     }
 
     // rolls over the given index if necessary
@@ -219,7 +243,9 @@
         return (index % mLog.length + mLog.length) % mLog.length;
     }
 
-    private class LogIterator implements ListIterator<String> {
+    private class LogIterator implements Iterator<String> {
+
+        private final @Nullable String mFilter;
 
         private final long mSystemTimeDeltaMs;
 
@@ -228,10 +254,17 @@
         private int mCount;
 
         LogIterator() {
+            this(null);
+        }
+
+        LogIterator(@Nullable String filter) {
+            mFilter = filter;
             mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
             mCurrentRealtimeMs = mStartRealtimeMs;
-            mIndex = startIndex();
-            mCount = 0;
+            mIndex = -1;
+            mCount = -1;
+
+            increment();
         }
 
         @Override
@@ -239,75 +272,17 @@
             return mCount < mLogSize;
         }
 
-        @Override
-        public boolean hasPrevious() {
-            return mCount > 0;
-        }
-
-        @Override
-        // return then increment
         public String next() {
             if (!hasNext()) {
                 throw new NoSuchElementException();
             }
 
             Log log = mLog[mIndex];
-            long nextDeltaMs = log.getTimeDeltaMs();
-            long realtimeMs = mCurrentRealtimeMs + nextDeltaMs;
+            long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs;
 
-            // calculate next index, skipping filler events
-            do {
-                mCurrentRealtimeMs += nextDeltaMs;
-                mIndex = incrementIndex(mIndex);
-                if (++mCount < mLogSize) {
-                    nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
-                }
-            } while (mCount < mLogSize && mLog[mIndex].isFiller());
+            increment();
 
-            return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
-        }
-
-        @Override
-        // decrement then return
-        public String previous() {
-            Log log;
-            long currentDeltaMs;
-            long realtimeMs;
-
-            // calculate previous index, skipping filler events with MAX_TIME_DELTA
-            do {
-                if (!hasPrevious()) {
-                    throw new NoSuchElementException();
-                }
-
-                mIndex = decrementIndex(mIndex);
-                mCount--;
-
-                log = mLog[mIndex];
-                realtimeMs = mCurrentRealtimeMs;
-
-                if (mCount > 0) {
-                    currentDeltaMs = log.getTimeDeltaMs();
-                    mCurrentRealtimeMs -= currentDeltaMs;
-                }
-            } while (mCount >= 0 && log.isFiller());
-
-            return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
-        }
-
-        @Override
-        public int nextIndex() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int previousIndex() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void add(String s) {
-            throw new UnsupportedOperationException();
+            return getTimePrefix(timeMs) + log.getLogString();
         }
 
         @Override
@@ -315,9 +290,16 @@
             throw new UnsupportedOperationException();
         }
 
-        @Override
-        public void set(String s) {
-            throw new UnsupportedOperationException();
+        private void increment() {
+            long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs();
+            do {
+                mCurrentRealtimeMs += nextDeltaMs;
+                mIndex = incrementIndex(mIndex);
+                if (++mCount < mLogSize) {
+                    nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
+                }
+            } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null
+                    && !mLog[mIndex].filter(mFilter))));
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
new file mode 100644
index 0000000..865d41f
--- /dev/null
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2021 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.location.eventlog;
+
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static android.util.TimeUtils.formatDuration;
+
+import static com.android.server.location.LocationManagerService.D;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.location.LocationRequest;
+import android.location.provider.ProviderRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Build;
+import android.os.PowerManager.LocationPowerSaveMode;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+/** In memory event log for location events. */
+public class LocationEventLog extends LocalEventLog {
+
+    private static int getLogSize() {
+        if (Build.IS_DEBUGGABLE || D) {
+            return 500;
+        } else {
+            return 200;
+        }
+    }
+
+    private static final int EVENT_LOCATION_ENABLED = 1;
+    private static final int EVENT_PROVIDER_ENABLED = 2;
+    private static final int EVENT_PROVIDER_MOCKED = 3;
+    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
+    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
+    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
+    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
+    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
+    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
+
+    @GuardedBy("mAggregateStats")
+    private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats;
+
+    public LocationEventLog() {
+        super(getLogSize());
+        mAggregateStats = new ArrayMap<>(4);
+    }
+
+    public ArrayMap<String, ArrayMap<String, AggregateStats>> copyAggregateStats() {
+        synchronized (mAggregateStats) {
+            ArrayMap<String, ArrayMap<String, AggregateStats>> copy = new ArrayMap<>(
+                    mAggregateStats);
+            for (int i = 0; i < copy.size(); i++) {
+                copy.setValueAt(i, new ArrayMap<>(copy.valueAt(i)));
+            }
+            return copy;
+        }
+    }
+
+    private AggregateStats getAggregateStats(String provider, String packageName) {
+        synchronized (mAggregateStats) {
+            ArrayMap<String, AggregateStats> packageMap = mAggregateStats.get(provider);
+            if (packageMap == null) {
+                packageMap = new ArrayMap<>(2);
+                mAggregateStats.put(provider, packageMap);
+            }
+            AggregateStats stats = packageMap.get(packageName);
+            if (stats == null) {
+                stats = new AggregateStats();
+                packageMap.put(packageName, stats);
+            }
+            return stats;
+        }
+    }
+
+    /** Logs a location enabled/disabled event. */
+    public void logLocationEnabled(int userId, boolean enabled) {
+        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+    }
+
+    /** Logs a location provider enabled/disabled event. */
+    public void logProviderEnabled(String provider, int userId, boolean enabled) {
+        addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
+    }
+
+    /** Logs a location provider being replaced/unreplaced by a mock provider. */
+    public void logProviderMocked(String provider, boolean mocked) {
+        addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
+    }
+
+    /** Logs a new client registration for a location provider. */
+    public void logProviderClientRegistered(String provider, CallerIdentity identity,
+            LocationRequest request) {
+        addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
+        getAggregateStats(provider, identity.getPackageName())
+                .markRequestAdded(request.getIntervalMillis());
+    }
+
+    /** Logs a client unregistration for a location provider. */
+    public void logProviderClientUnregistered(String provider, CallerIdentity identity) {
+        addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
+        getAggregateStats(provider, identity.getPackageName()).markRequestRemoved();
+    }
+
+    /** Logs a client for a location provider entering the active state. */
+    public void logProviderClientActive(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestActive();
+    }
+
+    /** Logs a client for a location provider leaving the active state. */
+    public void logProviderClientInactive(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestInactive();
+    }
+
+    /** Logs a client for a location provider entering the foreground state. */
+    public void logProviderClientForeground(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestForeground();
+    }
+
+    /** Logs a client for a location provider leaving the foreground state. */
+    public void logProviderClientBackground(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestBackground();
+    }
+
+    /** Logs a change to the provider request for a location provider. */
+    public void logProviderUpdateRequest(String provider, ProviderRequest request) {
+        addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
+    }
+
+    /** Logs a new incoming location for a location provider. */
+    public void logProviderReceivedLocations(String provider, int numLocations) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations);
+        }
+    }
+
+    /** Logs a location deliver for a client of a location provider. */
+    public void logProviderDeliveredLocations(String provider, int numLocations,
+            CallerIdentity identity) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
+        }
+        getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
+    }
+
+    /** Logs that the location power save mode has changed. */
+    public void logLocationPowerSaveMode(
+            @LocationPowerSaveMode int locationPowerSaveMode) {
+        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
+    }
+
+    @Override
+    protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
+        switch (event) {
+            case EVENT_LOCATION_ENABLED:
+                return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+            case EVENT_PROVIDER_ENABLED:
+                return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
+                        (Boolean) args[2]);
+            case EVENT_PROVIDER_MOCKED:
+                return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
+            case EVENT_PROVIDER_REGISTER_CLIENT:
+                return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
+                        (CallerIdentity) args[1], (LocationRequest) args[2]);
+            case EVENT_PROVIDER_UNREGISTER_CLIENT:
+                return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
+                        (CallerIdentity) args[1], null);
+            case EVENT_PROVIDER_UPDATE_REQUEST:
+                return new ProviderUpdateEvent(timeDelta, (String) args[0],
+                        (ProviderRequest) args[1]);
+            case EVENT_PROVIDER_RECEIVE_LOCATION:
+                return new ProviderReceiveLocationEvent(timeDelta, (String) args[0],
+                        (Integer) args[1]);
+            case EVENT_PROVIDER_DELIVER_LOCATION:
+                return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
+                        (Integer) args[1], (CallerIdentity) args[2]);
+            case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
+                return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    private abstract static class ProviderEvent extends LogEvent {
+
+        protected final String mProvider;
+
+        protected ProviderEvent(long timeDelta, String provider) {
+            super(timeDelta);
+            mProvider = provider;
+        }
+
+        @Override
+        public boolean filter(String filter) {
+            return mProvider.equals(filter);
+        }
+    }
+
+    private static final class ProviderEnabledEvent extends ProviderEvent {
+
+        private final int mUserId;
+        private final boolean mEnabled;
+
+        protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
+                boolean enabled) {
+            super(timeDelta, provider);
+            mUserId = userId;
+            mEnabled = enabled;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
+                    : "disabled");
+        }
+    }
+
+    private static final class ProviderMockedEvent extends ProviderEvent {
+
+        private final boolean mMocked;
+
+        protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
+            super(timeDelta, provider);
+            mMocked = mocked;
+        }
+
+        @Override
+        public String getLogString() {
+            if (mMocked) {
+                return mProvider + " provider added mock provider override";
+            } else {
+                return mProvider + " provider removed mock provider override";
+            }
+        }
+    }
+
+    private static final class ProviderRegisterEvent extends ProviderEvent {
+
+        private final boolean mRegistered;
+        private final CallerIdentity mIdentity;
+        @Nullable private final LocationRequest mLocationRequest;
+
+        private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
+                CallerIdentity identity, @Nullable LocationRequest locationRequest) {
+            super(timeDelta, provider);
+            mRegistered = registered;
+            mIdentity = identity;
+            mLocationRequest = locationRequest;
+        }
+
+        @Override
+        public String getLogString() {
+            if (mRegistered) {
+                return mProvider + " provider " + "+registration " + mIdentity + " -> "
+                        + mLocationRequest;
+            } else {
+                return mProvider + " provider " + "-registration " + mIdentity;
+            }
+        }
+    }
+
+    private static final class ProviderUpdateEvent extends ProviderEvent {
+
+        private final ProviderRequest mRequest;
+
+        private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
+            super(timeDelta, provider);
+            mRequest = request;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider request = " + mRequest;
+        }
+    }
+
+    private static final class ProviderReceiveLocationEvent extends ProviderEvent {
+
+        private final int mNumLocations;
+
+        private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) {
+            super(timeDelta, provider);
+            mNumLocations = numLocations;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider received location[" + mNumLocations + "]";
+        }
+    }
+
+    private static final class ProviderDeliverLocationEvent extends ProviderEvent {
+
+        private final int mNumLocations;
+        @Nullable private final CallerIdentity mIdentity;
+
+        private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations,
+                @Nullable CallerIdentity identity) {
+            super(timeDelta, provider);
+            mNumLocations = numLocations;
+            mIdentity = identity;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider delivered location[" + mNumLocations + "] to "
+                    + mIdentity;
+        }
+    }
+
+    private static final class LocationPowerSaveModeEvent extends LogEvent {
+
+        @LocationPowerSaveMode
+        private final int mLocationPowerSaveMode;
+
+        private LocationPowerSaveModeEvent(long timeDelta,
+                @LocationPowerSaveMode int locationPowerSaveMode) {
+            super(timeDelta);
+            mLocationPowerSaveMode = locationPowerSaveMode;
+        }
+
+        @Override
+        public String getLogString() {
+            String mode;
+            switch (mLocationPowerSaveMode) {
+                case LOCATION_MODE_NO_CHANGE:
+                    mode = "NO_CHANGE";
+                    break;
+                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+                    mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
+                    break;
+                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+                    mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
+                    break;
+                case LOCATION_MODE_FOREGROUND_ONLY:
+                    mode = "FOREGROUND_ONLY";
+                    break;
+                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+                    mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
+                    break;
+                default:
+                    mode = "UNKNOWN";
+                    break;
+            }
+            return "location power save mode changed to " + mode;
+        }
+    }
+
+    private static final class LocationEnabledEvent extends LogEvent {
+
+        private final int mUserId;
+        private final boolean mEnabled;
+
+        private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
+            super(timeDelta);
+            mUserId = userId;
+            mEnabled = enabled;
+        }
+
+        @Override
+        public String getLogString() {
+            return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+        }
+    }
+
+    /**
+     * Aggregate statistics for a single package under a single provider.
+     */
+    public static final class AggregateStats {
+
+        @GuardedBy("this")
+        private int mAddedRequestCount;
+        @GuardedBy("this")
+        private int mActiveRequestCount;
+        @GuardedBy("this")
+        private int mForegroundRequestCount;
+        @GuardedBy("this")
+        private int mDeliveredLocationCount;
+
+        @GuardedBy("this")
+        private long mFastestIntervalMs = Long.MAX_VALUE;
+        @GuardedBy("this")
+        private long mSlowestIntervalMs = 0;
+
+        @GuardedBy("this")
+        private long mAddedTimeTotalMs;
+        @GuardedBy("this")
+        private long mAddedTimeLastUpdateRealtimeMs;
+
+        @GuardedBy("this")
+        private long mActiveTimeTotalMs;
+        @GuardedBy("this")
+        private long mActiveTimeLastUpdateRealtimeMs;
+
+        @GuardedBy("this")
+        private long mForegroundTimeTotalMs;
+        @GuardedBy("this")
+        private long mForegroundTimeLastUpdateRealtimeMs;
+
+        AggregateStats() {}
+
+        synchronized void markRequestAdded(long intervalMillis) {
+            if (mAddedRequestCount++ == 0) {
+                mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+
+            mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
+            mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
+        }
+
+        synchronized void markRequestRemoved() {
+            updateTotals();
+            --mAddedRequestCount;
+            Preconditions.checkState(mAddedRequestCount >= 0);
+
+            mActiveRequestCount = min(mAddedRequestCount, mActiveRequestCount);
+            mForegroundRequestCount = min(mAddedRequestCount, mForegroundRequestCount);
+        }
+
+        synchronized void markRequestActive() {
+            Preconditions.checkState(mAddedRequestCount > 0);
+            if (mActiveRequestCount++ == 0) {
+                mActiveTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+        }
+
+        synchronized void markRequestInactive() {
+            updateTotals();
+            --mActiveRequestCount;
+            Preconditions.checkState(mActiveRequestCount >= 0);
+        }
+
+        synchronized void markRequestForeground() {
+            Preconditions.checkState(mAddedRequestCount > 0);
+            if (mForegroundRequestCount++ == 0) {
+                mForegroundTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+        }
+
+        synchronized void markRequestBackground() {
+            updateTotals();
+            --mForegroundRequestCount;
+            Preconditions.checkState(mForegroundRequestCount >= 0);
+        }
+
+        synchronized void markLocationDelivered() {
+            mDeliveredLocationCount++;
+        }
+
+        public synchronized void updateTotals() {
+            if (mAddedRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
+                mAddedTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+            if (mActiveRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mActiveTimeTotalMs += realtimeMs - mActiveTimeLastUpdateRealtimeMs;
+                mActiveTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+            if (mForegroundRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mForegroundTimeTotalMs += realtimeMs - mForegroundTimeLastUpdateRealtimeMs;
+                mForegroundTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+        }
+
+        @Override
+        public synchronized String toString() {
+            return "min/max interval = " + intervalToString(mFastestIntervalMs) + "/"
+                    + intervalToString(mSlowestIntervalMs)
+                    + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
+                    + "/" + formatDuration(mActiveTimeTotalMs) + "/"
+                    + formatDuration(mForegroundTimeTotalMs) + ", locations = "
+                    + mDeliveredLocationCount;
+        }
+
+        private static String intervalToString(long intervalMs) {
+            if (intervalMs == LocationRequest.PASSIVE_INTERVAL) {
+                return "passive";
+            } else {
+                return MILLISECONDS.toSeconds(intervalMs) + "s";
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 03938b2..0e157c2 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -56,7 +56,4 @@
 
     /** Returns a LocationUsageLogger. */
     LocationUsageLogger getLocationUsageLogger();
-
-    /** Returns a LocationEventLog. */
-    LocationEventLog getLocationEventLog();
 }
diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java
deleted file mode 100644
index 8d73518..0000000
--- a/services/core/java/com/android/server/location/injector/LocationEventLog.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.injector;
-
-import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
-import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
-import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
-import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
-import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
-
-import static com.android.server.location.LocationManagerService.D;
-
-import android.annotation.Nullable;
-import android.location.LocationRequest;
-import android.location.provider.ProviderRequest;
-import android.location.util.identity.CallerIdentity;
-import android.os.Build;
-import android.os.PowerManager.LocationPowerSaveMode;
-
-import com.android.server.location.eventlog.LocalEventLog;
-
-/** In memory event log for location events. */
-public class LocationEventLog extends LocalEventLog {
-
-    private static int getLogSize() {
-        if (Build.IS_DEBUGGABLE || D) {
-            return 500;
-        } else {
-            return 200;
-        }
-    }
-
-    private static final int EVENT_LOCATION_ENABLED = 1;
-    private static final int EVENT_PROVIDER_ENABLED = 2;
-    private static final int EVENT_PROVIDER_MOCKED = 3;
-    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
-    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
-    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
-    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
-    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
-    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
-
-    public LocationEventLog() {
-        super(getLogSize());
-    }
-
-    /** Logs a location enabled/disabled event. */
-    public void logLocationEnabled(int userId, boolean enabled) {
-        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
-    }
-
-    /** Logs a location provider enabled/disabled event. */
-    public void logProviderEnabled(String provider, int userId, boolean enabled) {
-        addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
-    }
-
-    /** Logs a location provider being replaced/unreplaced by a mock provider. */
-    public void logProviderMocked(String provider, boolean mocked) {
-        addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
-    }
-
-    /** Logs a new client registration for a location provider. */
-    public void logProviderClientRegistered(String provider, CallerIdentity identity,
-            LocationRequest request) {
-        addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
-    }
-
-    /** Logs a client unregistration for a location provider. */
-    public void logProviderClientUnregistered(String provider,
-            CallerIdentity identity) {
-        addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
-    }
-
-    /** Logs a change to the provider request for a location provider. */
-    public void logProviderUpdateRequest(String provider, ProviderRequest request) {
-        addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
-    }
-
-    /** Logs a new incoming location for a location provider. */
-    public void logProviderReceivedLocations(String provider, int numLocations) {
-        if (Build.IS_DEBUGGABLE || D) {
-            addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations);
-        }
-    }
-
-    /** Logs a location deliver for a client of a location provider. */
-    public void logProviderDeliveredLocations(String provider, int numLocations,
-            CallerIdentity identity) {
-        if (Build.IS_DEBUGGABLE || D) {
-            addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
-        }
-    }
-
-    /** Logs that the location power save mode has changed. */
-    public void logLocationPowerSaveMode(
-            @LocationPowerSaveMode int locationPowerSaveMode) {
-        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
-    }
-
-    @Override
-    protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
-        switch (event) {
-            case EVENT_LOCATION_ENABLED:
-                return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
-            case EVENT_PROVIDER_ENABLED:
-                return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
-                        (Boolean) args[2]);
-            case EVENT_PROVIDER_MOCKED:
-                return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
-            case EVENT_PROVIDER_REGISTER_CLIENT:
-                return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
-                        (CallerIdentity) args[1], (LocationRequest) args[2]);
-            case EVENT_PROVIDER_UNREGISTER_CLIENT:
-                return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
-                        (CallerIdentity) args[1], null);
-            case EVENT_PROVIDER_UPDATE_REQUEST:
-                return new ProviderUpdateEvent(timeDelta, (String) args[0],
-                        (ProviderRequest) args[1]);
-            case EVENT_PROVIDER_RECEIVE_LOCATION:
-                return new ProviderReceiveLocationEvent(timeDelta, (String) args[0],
-                        (Integer) args[1]);
-            case EVENT_PROVIDER_DELIVER_LOCATION:
-                return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
-                        (Integer) args[1], (CallerIdentity) args[2]);
-            case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
-                return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
-            default:
-                throw new AssertionError();
-        }
-    }
-
-    private static class ProviderEnabledEvent extends LogEvent {
-
-        private final String mProvider;
-        private final int mUserId;
-        private final boolean mEnabled;
-
-        protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
-                boolean enabled) {
-            super(timeDelta);
-            mProvider = provider;
-            mUserId = userId;
-            mEnabled = enabled;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
-                    : "disabled");
-        }
-    }
-
-    private static class ProviderMockedEvent extends LogEvent {
-
-        private final String mProvider;
-        private final boolean mMocked;
-
-        protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
-            super(timeDelta);
-            mProvider = provider;
-            mMocked = mocked;
-        }
-
-        @Override
-        public String getLogString() {
-            if (mMocked) {
-                return mProvider + " provider added mock provider override";
-            } else {
-                return mProvider + " provider removed mock provider override";
-            }
-        }
-    }
-
-    private static class ProviderRegisterEvent extends LogEvent {
-
-        private final String mProvider;
-        private final boolean mRegistered;
-        private final CallerIdentity mIdentity;
-        @Nullable private final LocationRequest mLocationRequest;
-
-        private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
-                CallerIdentity identity, @Nullable LocationRequest locationRequest) {
-            super(timeDelta);
-            mProvider = provider;
-            mRegistered = registered;
-            mIdentity = identity;
-            mLocationRequest = locationRequest;
-        }
-
-        @Override
-        public String getLogString() {
-            if (mRegistered) {
-                return mProvider + " provider " + "+registration " + mIdentity + " -> "
-                        + mLocationRequest;
-            } else {
-                return mProvider + " provider " + "-registration " + mIdentity;
-            }
-        }
-    }
-
-    private static class ProviderUpdateEvent extends LogEvent {
-
-        private final String mProvider;
-        private final ProviderRequest mRequest;
-
-        private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
-            super(timeDelta);
-            mProvider = provider;
-            mRequest = request;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider request = " + mRequest;
-        }
-    }
-
-    private static class ProviderReceiveLocationEvent extends LogEvent {
-
-        private final String mProvider;
-        private final int mNumLocations;
-
-        private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) {
-            super(timeDelta);
-            mProvider = provider;
-            mNumLocations = numLocations;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider received location[" + mNumLocations + "]";
-        }
-    }
-
-    private static class ProviderDeliverLocationEvent extends LogEvent {
-
-        private final String mProvider;
-        private final int mNumLocations;
-        @Nullable private final CallerIdentity mIdentity;
-
-        private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations,
-                @Nullable CallerIdentity identity) {
-            super(timeDelta);
-            mProvider = provider;
-            mNumLocations = numLocations;
-            mIdentity = identity;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider delivered location[" + mNumLocations + "] to "
-                    + mIdentity;
-        }
-    }
-
-    private static class LocationPowerSaveModeEvent extends LogEvent {
-
-        @LocationPowerSaveMode
-        private final int mLocationPowerSaveMode;
-
-        private LocationPowerSaveModeEvent(long timeDelta,
-                @LocationPowerSaveMode int locationPowerSaveMode) {
-            super(timeDelta);
-            mLocationPowerSaveMode = locationPowerSaveMode;
-        }
-
-        @Override
-        public String getLogString() {
-            String mode;
-            switch (mLocationPowerSaveMode) {
-                case LOCATION_MODE_NO_CHANGE:
-                    mode = "NO_CHANGE";
-                    break;
-                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
-                    mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
-                    break;
-                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
-                    mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
-                    break;
-                case LOCATION_MODE_FOREGROUND_ONLY:
-                    mode = "FOREGROUND_ONLY";
-                    break;
-                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
-                    mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
-                    break;
-                default:
-                    mode = "UNKNOWN";
-                    break;
-            }
-            return "location power save mode changed to " + mode;
-        }
-    }
-
-    private static class LocationEnabledEvent extends LogEvent {
-
-        private final int mUserId;
-        private final boolean mEnabled;
-
-        private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
-            super(timeDelta);
-            mUserId = userId;
-            mEnabled = enabled;
-        }
-
-        @Override
-        public String getLogString() {
-            return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index 532826a..cc00d56 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -24,6 +24,8 @@
 import android.os.PowerManager.LocationPowerSaveMode;
 import android.util.Log;
 
+import com.android.server.location.eventlog.LocationEventLog;
+
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index 1b74865..c47a64d 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,6 +25,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
 
 import java.util.Objects;
 import java.util.function.Consumer;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 48a012e..388b5a4 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -89,6 +89,7 @@
 import com.android.server.LocalServices;
 import com.android.server.location.LocationPermissions;
 import com.android.server.location.LocationPermissions.PermissionLevel;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.fudger.LocationFudger;
 import com.android.server.location.injector.AlarmHelper;
 import com.android.server.location.injector.AppForegroundHelper;
@@ -96,7 +97,6 @@
 import com.android.server.location.injector.AppOpsHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationAttributionHelper;
-import com.android.server.location.injector.LocationEventLog;
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
@@ -323,7 +323,7 @@
                         + getRequest());
             }
 
-            mLocationEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+            mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
 
             // initialization order is important as there are ordering dependencies
             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -333,6 +333,10 @@
             mIsUsingHighPower = isUsingHighPower();
 
             onProviderListenerRegister();
+
+            if (mForeground) {
+                mEventLog.logProviderClientForeground(mName, getIdentity());
+            }
         }
 
         @GuardedBy("mLock")
@@ -344,7 +348,7 @@
 
             onProviderListenerUnregister();
 
-            mLocationEventLog.logProviderClientUnregistered(mName, getIdentity());
+            mEventLog.logProviderClientUnregistered(mName, getIdentity());
 
             if (D) {
                 Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -369,6 +373,8 @@
                 Preconditions.checkState(Thread.holdsLock(mLock));
             }
 
+            mEventLog.logProviderClientActive(mName, getIdentity());
+
             if (!getRequest().isHiddenFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
             }
@@ -389,6 +395,8 @@
             }
 
             onProviderListenerInactive();
+
+            mEventLog.logProviderClientInactive(mName, getIdentity());
         }
 
         /**
@@ -524,6 +532,12 @@
 
                 mForeground = foreground;
 
+                if (mForeground) {
+                    mEventLog.logProviderClientForeground(mName, getIdentity());
+                } else {
+                    mEventLog.logProviderClientBackground(mName, getIdentity());
+                }
+
                 // note that onProviderLocationRequestChanged() is always called
                 return onProviderLocationRequestChanged()
                         || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
@@ -855,7 +869,7 @@
 
                     listener.deliverOnLocationChanged(deliverLocationResult,
                             mUseWakeLock ? mWakeLock::release : null);
-                    mLocationEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+                    mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
                             getIdentity());
                 }
 
@@ -1154,7 +1168,7 @@
 
                     // we currently don't hold a wakelock for getCurrentLocation deliveries
                     listener.deliverOnLocationChanged(deliverLocationResult, null);
-                    mLocationEventLog.logProviderDeliveredLocations(mName,
+                    mEventLog.logProviderDeliveredLocations(mName,
                             locationResult != null ? locationResult.size() : 0, getIdentity());
                 }
 
@@ -1223,6 +1237,7 @@
 
     private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
 
+    protected final LocationEventLog mEventLog;
     protected final LocationManagerInternal mLocationManagerInternal;
     protected final SettingsHelper mSettingsHelper;
     protected final UserInfoHelper mUserHelper;
@@ -1235,7 +1250,6 @@
     protected final LocationAttributionHelper mLocationAttributionHelper;
     protected final LocationUsageLogger mLocationUsageLogger;
     protected final LocationFudger mLocationFudger;
-    protected final LocationEventLog mLocationEventLog;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
     private final UserSettingChangedListener mLocationEnabledChangedListener =
@@ -1273,8 +1287,8 @@
     @GuardedBy("mLock")
     private @Nullable OnAlarmListener mDelayedRegister;
 
-    public LocationProviderManager(Context context, Injector injector, String name,
-            @Nullable PassiveLocationProviderManager passiveManager) {
+    public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+            String name, @Nullable PassiveLocationProviderManager passiveManager) {
         mContext = context;
         mName = Objects.requireNonNull(name);
         mPassiveManager = passiveManager;
@@ -1285,6 +1299,7 @@
         mEnabledListeners = new ArrayList<>();
         mProviderRequestListeners = new CopyOnWriteArrayList<>();
 
+        mEventLog = eventLog;
         mLocationManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(LocationManagerInternal.class));
         mSettingsHelper = injector.getSettingsHelper();
@@ -1297,7 +1312,6 @@
         mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
         mLocationAttributionHelper = injector.getLocationAttributionHelper();
         mLocationUsageLogger = injector.getLocationUsageLogger();
-        mLocationEventLog = injector.getLocationEventLog();
         mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
 
         mProvider = new MockableLocationProvider(mLock);
@@ -1437,7 +1451,7 @@
         synchronized (mLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
 
-            mLocationEventLog.logProviderMocked(mName, provider != null);
+            mEventLog.logProviderMocked(mName, provider != null);
 
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1925,7 +1939,7 @@
 
     @GuardedBy("mLock")
     private void setProviderRequest(ProviderRequest request) {
-        mLocationEventLog.logProviderUpdateRequest(mName, request);
+        mEventLog.logProviderUpdateRequest(mName, request);
         mProvider.getController().setRequest(request);
 
         FgThread.getHandler().post(() -> {
@@ -2261,7 +2275,7 @@
             }
 
             // don't log location received for passive provider because it's spammy
-            mLocationEventLog.logProviderReceivedLocations(mName, filtered.size());
+            mEventLog.logProviderReceivedLocations(mName, filtered.size());
         } else {
             // passive provider should get already filtered results as input
             filtered = locationResult;
@@ -2361,7 +2375,7 @@
             if (D) {
                 Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
             }
-            mLocationEventLog.logProviderEnabled(mName, userId, enabled);
+            mEventLog.logProviderEnabled(mName, userId, enabled);
         }
 
         // clear last locations if we become disabled
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f..027f4e9 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,6 +24,7 @@
 import android.os.Binder;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.Injector;
 
 import java.util.Collection;
@@ -33,8 +34,9 @@
  */
 public class PassiveLocationProviderManager extends LocationProviderManager {
 
-    public PassiveLocationProviderManager(Context context, Injector injector) {
-        super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
+    public PassiveLocationProviderManager(Context context, Injector injector,
+            LocationEventLog eventLog) {
+        super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 28c90e9..cb9793f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2372,10 +2372,17 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
         enforceShell();
+        final int origPid = Binder.getCallingPid();
+        final int origUid = Binder.getCallingUid();
+
+        // The original identity is an opaque integer.
         final long origId = Binder.clearCallingIdentity();
+        Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
         try {
-            (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec(
-                    this, in, out, err, args, callback, resultReceiver);
+            final LockSettingsShellCommand command =
+                    new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
+                            origUid);
+            command.exec(this, in, out, err, args, callback, resultReceiver);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 834cf05..67fae05 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -21,8 +21,11 @@
 
 import android.app.ActivityManager;
 import android.app.admin.PasswordMetrics;
+import android.content.Context;
 import android.os.ShellCommand;
+import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -43,15 +46,25 @@
     private static final String COMMAND_VERIFY = "verify";
     private static final String COMMAND_GET_DISABLED = "get-disabled";
     private static final String COMMAND_REMOVE_CACHE = "remove-cache";
+    private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE =
+            "set-resume-on-reboot-provider-package";
     private static final String COMMAND_HELP = "help";
 
     private int mCurrentUserId;
     private final LockPatternUtils mLockPatternUtils;
+    private final Context mContext;
+    private final int mCallingPid;
+    private final int mCallingUid;
+
     private String mOld = "";
     private String mNew = "";
 
-    LockSettingsShellCommand(LockPatternUtils lockPatternUtils) {
+    LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid,
+            int callingUid) {
         mLockPatternUtils = lockPatternUtils;
+        mCallingPid = callingPid;
+        mCallingUid = callingUid;
+        mContext = context;
     }
 
     @Override
@@ -68,6 +81,7 @@
                     case COMMAND_HELP:
                     case COMMAND_GET_DISABLED:
                     case COMMAND_SET_DISABLED:
+                    case COMMAND_SET_ROR_PROVIDER_PACKAGE:
                         break;
                     default:
                         getErrPrintWriter().println(
@@ -80,6 +94,9 @@
                 case COMMAND_REMOVE_CACHE:
                     runRemoveCache();
                     return 0;
+                case COMMAND_SET_ROR_PROVIDER_PACKAGE:
+                    runSetResumeOnRebootProviderPackage();
+                    return 0;
                 case COMMAND_HELP:
                     onHelp();
                     return 0;
@@ -170,6 +187,9 @@
             pw.println("");
             pw.println("  remove-cache [--user USER_ID]");
             pw.println("    Removes cached unified challenge for the managed profile.");
+            pw.println("  set-resume-on-reboot-provider-package <package_name>");
+            pw.println("    Sets the package name for server based resume on reboot service "
+                    + "provider.");
             pw.println("");
         }
     }
@@ -256,6 +276,17 @@
         return true;
     }
 
+    private boolean runSetResumeOnRebootProviderPackage() {
+        final String packageName = mNew;
+        String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE;
+        Slog.i(TAG, "Setting " +  name + " to " + packageName);
+
+        mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE,
+                mCallingPid, mCallingUid, TAG);
+        SystemProperties.set(name, packageName);
+        return true;
+    }
+
     private boolean runClear() {
         LockscreenCredential none = LockscreenCredential.createNone();
         if (!isNewCredentialSufficient(none)) {
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index a1e18bd..9c471b8 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -31,6 +31,7 @@
 import android.os.ParcelableException;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.resumeonreboot.IResumeOnRebootService;
@@ -55,6 +56,10 @@
             Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE;
     private static final String TAG = "ResumeOnRebootServiceProvider";
 
+    // The system property name that overrides the default service provider package name.
+    static final String PROP_ROR_PROVIDER_PACKAGE =
+            "persist.sys.resume_on_reboot_provider_package";
+
     private final Context mContext;
     private final PackageManager mPackageManager;
 
@@ -72,12 +77,19 @@
     private ServiceInfo resolveService() {
         Intent intent = new Intent();
         intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE);
-        if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
-            intent.setPackage(PROVIDER_PACKAGE);
+        int queryFlag = PackageManager.GET_SERVICES;
+        String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, "");
+        if (!testAppName.isEmpty()) {
+            Slog.i(TAG, "Using test app: " + testAppName);
+            intent.setPackage(testAppName);
+        } else {
+            queryFlag |= PackageManager.MATCH_SYSTEM_ONLY;
+            if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
+                intent.setPackage(PROVIDER_PACKAGE);
+            }
         }
 
-        List<ResolveInfo> resolvedIntents =
-                mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+        List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag);
         for (ResolveInfo resolvedInfo : resolvedIntents) {
             if (resolvedInfo.serviceInfo != null
                     && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) {
@@ -120,6 +132,7 @@
             if (mServiceConnection != null) {
                 mContext.unbindService(mServiceConnection);
             }
+            mBinder = null;
         }
 
         /** Bind to the service */
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 74111be..c462a92 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -55,7 +55,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
@@ -120,8 +122,8 @@
     private final Context mContext;
 
     private final Object mLock = new Object();
-    private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders =
-            new ArrayList<>();
+    private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
+            mControllerCallbackHolders = new CopyOnWriteArrayList<>();
 
     private long mFlags;
     private MediaButtonReceiverHolder mMediaButtonReceiverHolder;
@@ -545,51 +547,66 @@
     }
 
     private void pushPlaybackStateUpdate() {
+        PlaybackState playbackState;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onPlaybackStateChanged(mPlaybackState);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushPlaybackStateUpdate",
-                            holder, e);
+            playbackState = mPlaybackState;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onPlaybackStateChanged(playbackState);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushPlaybackStateUpdate", holder,
+                        e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushPlaybackStateUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushMetadataUpdate() {
+        MediaMetadata metadata;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onMetadataChanged(mMetadata);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
+            metadata = mMetadata;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onMetadataChanged(metadata);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushQueueUpdate() {
+        ParceledListSlice<QueueItem> parcelableQueue;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            ParceledListSlice<QueueItem> parcelableQueue;
             if (mQueue == null) {
                 parcelableQueue = null;
             } else {
@@ -598,77 +615,105 @@
                 // as onQueueChanged is an async binder call.
                 parcelableQueue.setInlineCountLimit(1);
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onQueueChanged(parcelableQueue);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onQueueChanged(parcelableQueue);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushQueueTitleUpdate() {
+        CharSequence queueTitle;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onQueueTitleChanged(mQueueTitle);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushQueueTitleUpdate",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
+            queueTitle = mQueueTitle;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onQueueTitleChanged(queueTitle);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushQueueTitleUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushExtrasUpdate() {
+        Bundle extras;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onExtrasChanged(mExtras);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+            extras = mExtras;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onExtrasChanged(extras);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushVolumeUpdate() {
+        PlaybackInfo info;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            PlaybackInfo info = getVolumeAttributes();
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onVolumeInfoChanged(info);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
+            info = getVolumeAttributes();
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onVolumeInfoChanged(info);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushEvent(String event, Bundle data) {
@@ -676,18 +721,24 @@
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onEvent(event, data);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushEvent", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushEvent", holder, e);
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onEvent(event, data);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushEvent", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushEvent", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushSessionDestroyed() {
@@ -697,21 +748,18 @@
             if (!mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onSessionDestroyed();
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushSessionDestroyed",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
-                }
-            }
-            // After notifying clear all listeners
-            mControllerCallbackHolders.clear();
         }
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onSessionDestroyed();
+            } catch (DeadObjectException e) {
+                logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
+            }
+        }
+        // After notifying clear all listeners
+        mControllerCallbackHolders.clear();
     }
 
     private PlaybackState getStateWithUpdatedPosition() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4c3dfbf..5703ffe 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,6 +110,8 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -284,7 +286,6 @@
 import com.android.server.notification.toast.TextToastRecord;
 import com.android.server.notification.toast.ToastRecord;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.quota.MultiRateLimiter;
@@ -358,7 +359,7 @@
     private static final int MESSAGE_RECONSIDER_RANKING = 1000;
     private static final int MESSAGE_RANKING_SORT = 1001;
 
-    static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
+    static final int LONG_DELAY = TOAST_WINDOW_TIMEOUT - TOAST_WINDOW_ANIM_BUFFER; // 3.5 seconds
     static final int SHORT_DELAY = 2000; // 2 seconds
 
     // 1 second past the ANR timeout.
@@ -3056,7 +3057,7 @@
                     // If the callback fails, this will remove it from the list, so don't
                     // assume that it's valid after this.
                     if (index == 0) {
-                        showNextToastLocked();
+                        showNextToastLocked(false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(callingId);
@@ -7392,7 +7393,7 @@
     }
 
     @GuardedBy("mToastQueue")
-    void showNextToastLocked() {
+    void showNextToastLocked(boolean lastToastWasTextRecord) {
         if (mIsCurrentToastShown) {
             return; // Don't show the same toast twice.
         }
@@ -7406,7 +7407,7 @@
                     mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
 
             if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
-                scheduleDurationReachedLocked(record);
+                scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                 mIsCurrentToastShown = true;
                 if (rateLimitingEnabled) {
                     mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
@@ -7477,7 +7478,7 @@
             // Show the next one. If the callback fails, this will remove
             // it from the list, so don't assume that the list hasn't changed
             // after this point.
-            showNextToastLocked();
+            showNextToastLocked(lastToast instanceof TextToastRecord);
         }
     }
 
@@ -7491,7 +7492,7 @@
     }
 
     @GuardedBy("mToastQueue")
-    private void scheduleDurationReachedLocked(ToastRecord r)
+    private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord)
     {
         mHandler.removeCallbacksAndMessages(r);
         Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
@@ -7501,6 +7502,14 @@
         // preference.
         delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
                 AccessibilityManager.FLAG_CONTENT_TEXT);
+
+        if (lastToastWasTextRecord) {
+            delay += 250; // delay to account for previous toast's "out" animation
+        }
+        if (r instanceof TextToastRecord) {
+            delay += 333; // delay to account for this toast's "in" animation
+        }
+
         mHandler.sendMessageDelayed(m, delay);
     }
 
diff --git a/services/core/java/com/android/server/om/DumpState.java b/services/core/java/com/android/server/om/DumpState.java
index 1e2e054..88afcb2 100644
--- a/services/core/java/com/android/server/om/DumpState.java
+++ b/services/core/java/com/android/server/om/DumpState.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.om.OverlayIdentifier;
 import android.os.UserHandle;
 
 /**
@@ -26,6 +27,7 @@
 public final class DumpState {
     @UserIdInt private int mUserId = UserHandle.USER_ALL;
     @Nullable private String mPackageName;
+    @Nullable private String mOverlayName;
     @Nullable private String mField;
     private boolean mVerbose;
 
@@ -38,14 +40,19 @@
     }
 
     /** Sets the name of the package to dump the state for */
-    public void setPackageName(String packageName) {
-        mPackageName = packageName;
+    public void setOverlyIdentifier(String overlayIdentifier) {
+        final OverlayIdentifier overlay = OverlayIdentifier.fromString(overlayIdentifier);
+        mPackageName = overlay.getPackageName();
+        mOverlayName = overlay.getOverlayName();
     }
     @Nullable public String getPackageName() {
         return mPackageName;
     }
+    @Nullable public String getOverlayName() {
+        return mOverlayName;
+    }
 
-    /** Sets the name of the field to dump the state of */
+    /** Sets the name of the field to dump the state for */
     public void setField(String field) {
         mField = field;
     }
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 19fa920..2ebc8ed 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -20,17 +20,23 @@
 
 import static com.android.server.om.OverlayManagerService.TAG;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.os.IBinder;
 import android.os.IIdmap2;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemService;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.server.FgThread;
 
 import java.io.File;
+import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -104,10 +110,12 @@
         return sInstance;
     }
 
-    String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-            int userId) throws TimeoutException, RemoteException {
+    String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+            @Nullable String overlayName, int policies, boolean enforce, int userId)
+            throws TimeoutException, RemoteException {
         try (Connection c = connect()) {
-            return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId);
+            return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+                    policies, enforce, userId);
         }
     }
 
@@ -117,11 +125,12 @@
         }
     }
 
-    boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-             int userId)
+    boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+            @Nullable String overlayName, int policies, boolean enforce, int userId)
             throws Exception {
         try (Connection c = connect()) {
-            return mService.verifyIdmap(targetPath, overlayPath, policies, enforce, userId);
+            return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+                    policies, enforce, userId);
         }
     }
 
@@ -129,12 +138,38 @@
         try (Connection c = connect()) {
             return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
         } catch (Exception e) {
-            Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
-                    + e.getMessage());
+            Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
             return false;
         }
     }
 
+    FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+        try (Connection c = connect()) {
+            return mService.createFabricatedOverlay(overlay);
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
+            return null;
+        }
+    }
+
+    boolean deleteFabricatedOverlay(@NonNull String path) {
+        try (Connection c = connect()) {
+            return mService.deleteFabricatedOverlay(path);
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
+            return false;
+        }
+    }
+
+    List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        try (Connection c = connect()) {
+            return mService.getFabricatedOverlayInfos();
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to get fabricated overlays", e);
+            return null;
+        }
+    }
+
     private IBinder getIdmapService() throws TimeoutException, RemoteException {
         SystemService.start(IDMAP_DAEMON);
 
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index eeb2655..64362c9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -22,15 +22,19 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser;
 import android.os.Build.VERSION_CODES;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.os.OverlayablePolicy;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Slog;
 
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
 import java.io.IOException;
+import java.util.List;
 
 /**
  * Handle the creation and deletion of idmap files.
@@ -74,25 +78,26 @@
      * Creates the idmap for the target/overlay combination and returns whether the idmap file was
      * modified.
      */
-    boolean createIdmap(@NonNull final PackageInfo targetPackage,
-            @NonNull final PackageInfo overlayPackage, int userId) {
+    boolean createIdmap(@NonNull final AndroidPackage targetPackage,
+            @NonNull final AndroidPackage overlayPackage, String overlayBasePath,
+            String overlayName, int userId) {
         if (DEBUG) {
-            Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
-                    + overlayPackage.packageName);
+            Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and "
+                    + overlayPackage.getPackageName());
         }
-        final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
-        final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
+        final String targetPath = targetPackage.getBaseApkPath();
         try {
             int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
             boolean enforce = enforceOverlayable(overlayPackage);
-            if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
+            if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies,
+                    enforce, userId)) {
                 return false;
             }
-            return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
+            return mIdmapDaemon.createIdmap(targetPath, overlayBasePath, overlayName, policies,
                     enforce, userId) != null;
         } catch (Exception e) {
             Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
-                    + overlayPath + ": " + e.getMessage());
+                    + overlayBasePath, e);
             return false;
         }
     }
@@ -104,7 +109,7 @@
         try {
             return mIdmapDaemon.removeIdmap(oi.baseCodePath, userId);
         } catch (Exception e) {
-            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
+            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath, e);
             return false;
         }
     }
@@ -113,22 +118,40 @@
         return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
     }
 
-    boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
-        return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
+    /**
+     * @return the list of all fabricated overlays
+     */
+    List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        return mIdmapDaemon.getFabricatedOverlayInfos();
+    }
+
+    /**
+     * Creates a fabricated overlay and persists it to disk.
+     * @return the path to the fabricated overlay
+     */
+    FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+        return mIdmapDaemon.createFabricatedOverlay(overlay);
+    }
+
+    /**
+     * Deletes the fabricated overlay file on disk.
+     * @return whether the path was deleted
+     */
+    boolean deleteFabricatedOverlay(@NonNull String path) {
+        return mIdmapDaemon.deleteFabricatedOverlay(path);
     }
 
     /**
      * Checks if overlayable and policies should be enforced on the specified overlay for backwards
      * compatibility with pre-Q overlays.
      */
-    private boolean enforceOverlayable(@NonNull final PackageInfo overlayPackage) {
-        final ApplicationInfo ai = overlayPackage.applicationInfo;
-        if (ai.targetSdkVersion >= VERSION_CODES.Q) {
+    private boolean enforceOverlayable(@NonNull final AndroidPackage overlayPackage) {
+        if (overlayPackage.getTargetSdkVersion() >= VERSION_CODES.Q) {
             // Always enforce policies for overlays targeting Q+.
             return true;
         }
 
-        if (ai.isVendor()) {
+        if (overlayPackage.isVendor()) {
             // If the overlay is on a pre-Q vendor partition, do not enforce overlayable
             // restrictions on this overlay because the pre-Q platform has no understanding of
             // overlayable.
@@ -137,20 +160,19 @@
 
         // Do not enforce overlayable restrictions on pre-Q overlays that are signed with the
         // platform signature or that are preinstalled.
-        return !(ai.isSystemApp() || ai.isSignedWithPlatformKey());
+        return !(overlayPackage.isSystem() || overlayPackage.isSignedWithPlatformKey());
     }
 
     /**
      * Retrieves a bitmask for idmap2 that represents the policies the overlay fulfills.
      */
-    private int calculateFulfilledPolicies(@NonNull final PackageInfo targetPackage,
-            @NonNull final PackageInfo overlayPackage, int userId)  {
-        final ApplicationInfo ai = overlayPackage.applicationInfo;
+    private int calculateFulfilledPolicies(@NonNull final AndroidPackage targetPackage,
+            @NonNull final AndroidPackage overlayPackage, int userId)  {
         int fulfilledPolicies = OverlayablePolicy.PUBLIC;
 
         // Overlay matches target signature
-        if (mPackageManager.signaturesMatching(targetPackage.packageName,
-                overlayPackage.packageName, userId)) {
+        if (mPackageManager.signaturesMatching(targetPackage.getPackageName(),
+                overlayPackage.getPackageName(), userId)) {
             fulfilledPolicies |= OverlayablePolicy.SIGNATURE;
         }
 
@@ -164,52 +186,52 @@
         // preinstalled package, check if overlay matches its signature.
         if (!TextUtils.isEmpty(mConfigSignaturePackage)
                 && mPackageManager.signaturesMatching(mConfigSignaturePackage,
-                                                           overlayPackage.packageName,
+                                                           overlayPackage.getPackageName(),
                                                            userId)) {
             fulfilledPolicies |= OverlayablePolicy.CONFIG_SIGNATURE;
         }
 
         // Vendor partition (/vendor)
-        if (ai.isVendor()) {
+        if (overlayPackage.isVendor()) {
             return fulfilledPolicies | OverlayablePolicy.VENDOR_PARTITION;
         }
 
         // Product partition (/product)
-        if (ai.isProduct()) {
+        if (overlayPackage.isProduct()) {
             return fulfilledPolicies | OverlayablePolicy.PRODUCT_PARTITION;
         }
 
         // Odm partition (/odm)
-        if (ai.isOdm()) {
+        if (overlayPackage.isOdm()) {
             return fulfilledPolicies | OverlayablePolicy.ODM_PARTITION;
         }
 
         // Oem partition (/oem)
-        if (ai.isOem()) {
+        if (overlayPackage.isOem()) {
             return fulfilledPolicies | OverlayablePolicy.OEM_PARTITION;
         }
 
         // System_ext partition (/system_ext) is considered as system
         // Check this last since every partition except for data is scanned as system in the PMS.
-        if (ai.isSystemApp() || ai.isSystemExt()) {
+        if (overlayPackage.isSystem() || overlayPackage.isSystemExt()) {
             return fulfilledPolicies | OverlayablePolicy.SYSTEM_PARTITION;
         }
 
         return fulfilledPolicies;
     }
 
-    private boolean matchesActorSignature(@NonNull PackageInfo targetPackage,
-            @NonNull PackageInfo overlayPackage, int userId) {
-        String targetOverlayableName = overlayPackage.targetOverlayableName;
+    private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
+            @NonNull AndroidPackage overlayPackage, int userId) {
+        String targetOverlayableName = overlayPackage.getOverlayTargetName();
         if (targetOverlayableName != null) {
             try {
                 OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
-                        targetPackage.packageName, targetOverlayableName, userId);
+                        targetPackage.getPackageName(), targetOverlayableName, userId);
                 if (overlayableInfo != null && overlayableInfo.actor != null) {
                     String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
                             overlayableInfo.actor, mPackageManager.getNamedActors()).first;
                     if (mPackageManager.signaturesMatching(actorPackageName,
-                            overlayPackage.packageName, userId)) {
+                            overlayPackage.getPackageName(), userId)) {
                         return true;
                     }
                 }
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 8121a43e9..2d540de 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,8 +19,6 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.net.Uri;
 import android.os.Process;
 import android.text.TextUtils;
@@ -29,6 +27,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.IOException;
 import java.util.List;
@@ -114,12 +113,13 @@
         }
 
         final String targetPackageName = overlayInfo.targetPackageName;
-        final PackageInfo targetPkgInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
+        final AndroidPackage targetPkgInfo = mPackageManager.getPackageForUser(targetPackageName,
+                userId);
         if (targetPkgInfo == null) {
             return ActorState.TARGET_NOT_FOUND;
         }
 
-        if ((targetPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+        if (targetPkgInfo.isDebuggable()) {
             return ActorState.ALLOWED;
         }
 
@@ -189,23 +189,18 @@
             return actorUriState;
         }
 
-        String packageName = actorUriPair.first;
-        PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, userId);
-        if (packageInfo == null) {
-            return ActorState.MISSING_APP_INFO;
-        }
-
-        ApplicationInfo appInfo = packageInfo.applicationInfo;
-        if (appInfo == null) {
-            return ActorState.MISSING_APP_INFO;
+        String actorPackageName = actorUriPair.first;
+        AndroidPackage actorPackage = mPackageManager.getPackageForUser(actorPackageName, userId);
+        if (actorPackage == null) {
+            return ActorState.ACTOR_NOT_FOUND;
         }
 
         // Currently only pre-installed apps can be actors
-        if (!appInfo.isSystemApp()) {
+        if (!actorPackage.isSystem()) {
             return ActorState.ACTOR_NOT_PREINSTALLED;
         }
 
-        if (ArrayUtils.contains(callingPackageNames, packageName)) {
+        if (ArrayUtils.contains(callingPackageNames, actorPackageName)) {
             return ActorState.ALLOWED;
         }
 
@@ -231,7 +226,7 @@
         NO_NAMED_ACTORS,
         MISSING_NAMESPACE,
         MISSING_ACTOR_NAME,
-        MISSING_APP_INFO,
+        ACTOR_NOT_FOUND,
         ACTOR_NOT_PREINSTALLED,
         INVALID_ACTOR,
         ALLOWED
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index fd2fb1f..905733c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -24,8 +24,10 @@
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_REASON;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
@@ -43,11 +45,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManagerTransaction;
 import android.content.om.OverlayableInfo;
 import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.overlay.OverlayPaths;
@@ -55,8 +57,10 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.FabricatedOverlayInternal;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -71,12 +75,15 @@
 import android.util.SparseArray;
 
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
+
 import com.android.server.pm.UserManagerService;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import libcore.util.EmptyArray;
 
@@ -92,13 +99,12 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Consumer;
 
 /**
  * Service to manage asset overlays.
@@ -247,15 +253,6 @@
 
     private final OverlayActorEnforcer mActorEnforcer;
 
-    private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
-        persistSettings();
-        FgThread.getHandler().post(() -> {
-            List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
-            updateActivityManager(affectedTargets, pair.userId);
-            broadcastActionOverlayChanged(pair.packageName, pair.userId);
-        });
-    };
-
     public OverlayManagerService(@NonNull final Context context) {
         super(context);
         try {
@@ -315,8 +312,7 @@
                     // Initialize any users that can't be switched to, as their state would
                     // never be setup in onSwitchUser(). We will switch to the system user right
                     // after this, and its state will be setup there.
-                    final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id);
-                    updatePackageManager(targets, users.get(i).id);
+                    updatePackageManager(mImpl.updateOverlaysForUser(users.get(i).id));
                 }
             }
         }
@@ -333,9 +329,7 @@
             // ensure overlays in the settings are up-to-date, and propagate
             // any asset changes to the rest of the system
             synchronized (mLock) {
-                final List<String> targets = mImpl.updateOverlaysForUser(newUserId);
-                final List<String> affectedTargets = updatePackageManager(targets, newUserId);
-                updateActivityManager(affectedTargets, newUserId);
+                updateActivityManager(updatePackageManager(mImpl.updateOverlaysForUser(newUserId)));
             }
             persistSettings();
         } finally {
@@ -417,19 +411,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
                 for (final int userId : userIds) {
                     synchronized (mLock) {
-                        final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
-                                false);
-                        if (pi != null && !pi.applicationInfo.isInstantApp()) {
-                            mPackageManager.cachePackageInfo(packageName, userId, pi);
-
+                        final AndroidPackage pkg = mPackageManager.onPackageAdded(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                if (pi.isOverlayPackage()) {
-                                    mImpl.onOverlayPackageAdded(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                } else {
-                                    mImpl.onTargetPackageAdded(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }
+                                updateTargetPackages(mImpl.onPackageAdded(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageAdded internal error", e);
                             }
@@ -447,19 +433,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
-                                false);
-                        if (pi != null && pi.applicationInfo.isInstantApp()) {
-                            mPackageManager.cachePackageInfo(packageName, userId, pi);
-
+                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                if (pi.isOverlayPackage()) {
-                                    mImpl.onOverlayPackageChanged(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }  else {
-                                    mImpl.onTargetPackageChanged(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }
+                                updateTargetPackages(mImpl.onPackageChanged(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageChanged internal error", e);
                             }
@@ -477,12 +455,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        mPackageManager.forgetPackageInfo(packageName, userId);
-                        final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
-                        if (oi != null) {
+                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                mImpl.onOverlayPackageReplacing(packageName, userId)
-                                    .ifPresent(mPropagateOverlayChange);
+                                updateTargetPackages(mImpl.onPackageReplacing(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageReplacing internal error", e);
                             }
@@ -500,18 +477,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
-                                false);
-                        if (pi != null && !pi.applicationInfo.isInstantApp()) {
-                            mPackageManager.cachePackageInfo(packageName, userId, pi);
+                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                if (pi.isOverlayPackage()) {
-                                    mImpl.onOverlayPackageReplaced(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                } else {
-                                    mImpl.onTargetPackageReplaced(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }
+                                updateTargetPackages(mImpl.onPackageReplaced(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageReplaced internal error", e);
                             }
@@ -529,20 +499,8 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        mPackageManager.forgetPackageInfo(packageName, userId);
-                        final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
-
-                        try {
-                            if (oi != null) {
-                                mImpl.onOverlayPackageRemoved(packageName, userId)
-                                    .ifPresent(mPropagateOverlayChange);
-                            } else {
-                                mImpl.onTargetPackageRemoved(packageName, userId)
-                                    .ifPresent(mPropagateOverlayChange);
-                            }
-                        } catch (OperationFailedException e) {
-                            Slog.e(TAG, "onPackageRemoved internal error", e);
-                        }
+                        mPackageManager.onPackageRemoved(packageName, userId);
+                        updateTargetPackages(mImpl.onPackageRemoved(packageName, userId));
                     }
                 }
             } finally {
@@ -560,11 +518,9 @@
                     if (userId != UserHandle.USER_NULL) {
                         try {
                             traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
-                            final ArrayList<String> targets;
                             synchronized (mLock) {
-                                targets = mImpl.updateOverlaysForUser(userId);
+                                updatePackageManager(mImpl.updateOverlaysForUser(userId));
                             }
-                            updatePackageManager(targets, userId);
                         } finally {
                             traceEnd(TRACE_TAG_RRO);
                         }
@@ -628,16 +584,22 @@
         @Override
         public OverlayInfo getOverlayInfo(@Nullable final String packageName,
                 final int userIdArg) {
-            if (packageName == null) {
+            return getOverlayInfoByIdentifier(new OverlayIdentifier(packageName), userIdArg);
+        }
+
+        @Override
+        public OverlayInfo getOverlayInfoByIdentifier(@Nullable final OverlayIdentifier overlay,
+                final int userIdArg) {
+            if (overlay == null || overlay.getPackageName() == null) {
                 return null;
             }
 
             try {
-                traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + packageName);
+                traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + overlay);
                 final int realUserId = handleIncomingUser(userIdArg, "getOverlayInfo");
 
                 synchronized (mLock) {
-                    return mImpl.getOverlayInfo(packageName, realUserId);
+                    return mImpl.getOverlayInfo(overlay, realUserId);
                 }
             } finally {
                 traceEnd(TRACE_TAG_RRO);
@@ -653,15 +615,16 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setEnabled");
-                enforceActor(packageName, "setEnabled", realUserId);
+                enforceActor(overlay, "setEnabled", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabled(packageName, enable, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            updateTargetPackages(mImpl.setEnabled(overlay, enable, realUserId));
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -684,16 +647,18 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setEnabledExclusive");
-                enforceActor(packageName, "setEnabledExclusive", realUserId);
+                enforceActor(overlay, "setEnabledExclusive", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabledExclusive(packageName,
+                            mImpl.setEnabledExclusive(overlay,
                                     false /* withinCategory */, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -716,17 +681,19 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg,
                         "setEnabledExclusiveInCategory");
-                enforceActor(packageName, "setEnabledExclusiveInCategory", realUserId);
+                enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabledExclusive(packageName,
+                            mImpl.setEnabledExclusive(overlay,
                                     true /* withinCategory */, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -750,15 +717,18 @@
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
                         + parentPackageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
+                final OverlayIdentifier parentOverlay = new OverlayIdentifier(parentPackageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setPriority");
-                enforceActor(packageName, "setPriority", realUserId);
+                enforceActor(overlay, "setPriority", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setPriority(packageName, parentPackageName, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            mImpl.setPriority(overlay, parentOverlay, realUserId)
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -780,15 +750,16 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setHighestPriority");
-                enforceActor(packageName, "setHighestPriority", realUserId);
+                enforceActor(overlay, "setHighestPriority", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setHighestPriority(packageName, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            updateTargetPackages(mImpl.setHighestPriority(overlay, realUserId));
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -810,15 +781,17 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setLowestPriority");
-                enforceActor(packageName, "setLowestPriority", realUserId);
+                enforceActor(overlay, "setLowestPriority", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setLowestPriority(packageName, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            mImpl.setLowestPriority(overlay, realUserId)
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -858,12 +831,17 @@
                 return;
             }
 
+            final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
             final int realUserId = handleIncomingUser(userIdArg, "invalidateCachesForOverlay");
-            enforceActor(packageName, "invalidateCachesForOverlay", realUserId);
+            enforceActor(overlay, "invalidateCachesForOverlay", realUserId);
             final long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    mImpl.removeIdmapForOverlay(packageName, realUserId);
+                    try {
+                        mImpl.removeIdmapForOverlay(overlay, realUserId);
+                    } catch (OperationFailedException e) {
+                        Slog.w(TAG, "invalidate caches for overlay '" + overlay + "' failed", e);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -893,25 +871,63 @@
             }
         }
 
-        private Optional<PackageAndUser> executeRequest(
-                @NonNull final OverlayManagerTransaction.Request request) throws Exception {
-            final int realUserId = handleIncomingUser(request.userId, request.typeToString());
-            enforceActor(request.packageName, request.typeToString(), realUserId);
+        private Set<PackageAndUser> executeRequest(
+                @NonNull final OverlayManagerTransaction.Request request)
+                throws OperationFailedException {
+            Objects.requireNonNull(request, "Transaction contains a null request");
+            Objects.requireNonNull(request.overlay,
+                    "Transaction overlay identifier must be non-null");
+
+            final int callingUid = Binder.getCallingUid();
+            final int realUserId;
+            if (request.type == TYPE_REGISTER_FABRICATED
+                    || request.type == TYPE_UNREGISTER_FABRICATED) {
+                if (request.userId != UserHandle.USER_ALL) {
+                    throw new IllegalArgumentException(request.typeToString()
+                            + " unsupported for user " + request.userId);
+                }
+                realUserId = UserHandle.USER_ALL;
+
+                // Enforce that the calling process can only register and unregister fabricated
+                // overlays using its package name.
+                final String pkgName = request.overlay.getPackageName();
+                if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
+                        mPackageManager.getPackagesForUid(callingUid), pkgName)) {
+                    throw new IllegalArgumentException("UID " + callingUid + " does own package"
+                            + "name " + pkgName);
+                }
+            } else {
+                // Enforce actor requirements for enabling, disabling, and reordering overlays.
+                realUserId = handleIncomingUser(request.userId, request.typeToString());
+                enforceActor(request.overlay, request.typeToString(), realUserId);
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
                 switch (request.type) {
                     case TYPE_SET_ENABLED:
-                        Optional<PackageAndUser> opt1 =
-                                mImpl.setEnabled(request.packageName, true, request.userId);
-                        Optional<PackageAndUser> opt2 =
-                                mImpl.setHighestPriority(request.packageName, request.userId);
-                        // Both setEnabled and setHighestPriority affected the same
-                        // target package and user: if both return non-empty
-                        // Optionals, they are identical
-                        return opt1.isPresent() ? opt1 : opt2;
+                        Set<PackageAndUser> result = null;
+                        result = CollectionUtils.addAll(result,
+                                mImpl.setEnabled(request.overlay, true, realUserId));
+                        result = CollectionUtils.addAll(result,
+                                mImpl.setHighestPriority(request.overlay, realUserId));
+                        return CollectionUtils.emptyIfNull(result);
+
                     case TYPE_SET_DISABLED:
-                        return mImpl.setEnabled(request.packageName, false, request.userId);
+                        return mImpl.setEnabled(request.overlay, false, realUserId);
+
+                    case TYPE_REGISTER_FABRICATED:
+                        final FabricatedOverlayInternal fabricated =
+                                request.extras.getParcelable(
+                                        OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY
+                                );
+                        Objects.requireNonNull(fabricated,
+                                "no fabricated overlay attached to request");
+                        return mImpl.registerFabricatedOverlay(fabricated);
+
+                    case TYPE_UNREGISTER_FABRICATED:
+                        return mImpl.unregisterFabricatedOverlay(request.overlay);
+
                     default:
                         throw new IllegalArgumentException("unsupported request: " + request);
                 }
@@ -921,7 +937,7 @@
         }
 
         private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
-                throws Exception {
+                throws OperationFailedException {
             if (DEBUG) {
                 Slog.d(TAG, "commit " + transaction);
             }
@@ -931,25 +947,25 @@
 
             // map: userId -> set<package-name>: target packages of overlays in
             // this transaction
-            SparseArray<Set<String>> transactionTargets = new SparseArray<>();
+            final SparseArray<Set<String>> transactionTargets = new SparseArray<>();
 
             // map: userId -> set<package-name>: packages that need to reload
             // their resources due to changes to the overlays in this
             // transaction
-            SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
+            final SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
 
             synchronized (mLock) {
-
                 // execute the requests (as calling user)
                 for (final OverlayManagerTransaction.Request request : transaction) {
-                    executeRequest(request).ifPresent(target -> {
-                        Set<String> userTargets = transactionTargets.get(target.userId);
-                        if (userTargets == null) {
-                            userTargets = new ArraySet<String>();
-                            transactionTargets.put(target.userId, userTargets);
-                        }
-                        userTargets.add(target.packageName);
-                    });
+                    executeRequest(request).forEach(
+                            target -> {
+                                Set<String> userTargets = transactionTargets.get(target.userId);
+                                if (userTargets == null) {
+                                    userTargets = new ArraySet<>();
+                                    transactionTargets.put(target.userId, userTargets);
+                                }
+                                userTargets.add(target.packageName);
+                            });
                 }
 
                 // past the point of no return: the entire transaction has been
@@ -975,10 +991,7 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     // schedule apps to refresh
-                    for (int index = 0; index < affectedPackagesToUpdate.size(); index++) {
-                        final int userId = affectedPackagesToUpdate.keyAt(index);
-                        updateActivityManager(affectedPackagesToUpdate.valueAt(index), userId);
-                    }
+                    updateActivityManager(affectedPackagesToUpdate);
 
                     // broadcast the ACTION_OVERLAY_CHANGED intents
                     for (int index = 0; index < transactionTargets.size(); index++) {
@@ -1059,12 +1072,12 @@
                         dumpState.setField(arg);
                         break;
                     default:
-                        dumpState.setPackageName(arg);
+                        dumpState.setOverlyIdentifier(arg);
                         break;
                 }
             }
             if (dumpState.getPackageName() == null && opti < args.length) {
-                dumpState.setPackageName(args[opti]);
+                dumpState.setOverlyIdentifier(args[opti]);
                 opti++;
             }
 
@@ -1101,12 +1114,12 @@
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
         }
 
-        private void enforceActor(String packageName, String methodName, int realUserId)
-                throws SecurityException {
-            OverlayInfo overlayInfo = mImpl.getOverlayInfo(packageName, realUserId);
+        private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
+                int realUserId) throws SecurityException {
+            OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
             if (overlayInfo == null) {
                 throw new IllegalArgumentException("Unable to retrieve overlay information for "
-                        + packageName);
+                        + overlay);
             }
 
             int callingUid = Binder.getCallingUid();
@@ -1115,7 +1128,13 @@
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
-
+        private static class AndroidPackageUsers {
+            private AndroidPackage mPackage;
+            private final Set<Integer> mInstalledUsers = new ArraySet<>();
+            private AndroidPackageUsers(@NonNull AndroidPackage pkg) {
+                this.mPackage = pkg;
+            }
+        }
         private final Context mContext;
         private final IPackageManager mPackageManager;
         private final PackageManagerInternal mPackageManagerInternal;
@@ -1125,7 +1144,8 @@
         // intent, querying the PackageManagerService for the actual current
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
-        private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
+        private final ArrayMap<String, AndroidPackageUsers> mCache = new ArrayMap<>();
+        private final Set<Integer> mInitializedUsers = new ArraySet<>();
 
         PackageManagerHelperImpl(Context context) {
             mContext = context;
@@ -1133,29 +1153,112 @@
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
 
-        public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
-                final boolean useCache) {
-            if (useCache) {
-                final PackageInfo cachedPi = getCachedPackageInfo(packageName, userId);
-                if (cachedPi != null) {
-                    return cachedPi;
+        /**
+         * Initializes the helper for the user. This only needs to be invoked one time before
+         * packages of this user are queried.
+         * @param userId the user id to initialize
+         * @return a map of package name to all packages installed in the user
+         */
+        @NonNull
+        public ArrayMap<String, AndroidPackage> initializeForUser(final int userId) {
+            if (!mInitializedUsers.contains(userId)) {
+                mInitializedUsers.add(userId);
+                mPackageManagerInternal.forEachInstalledPackage(
+                        (pkg) -> addPackageUser(pkg, userId), userId);
+            }
+
+            final ArrayMap<String, AndroidPackage> userPackages = new ArrayMap<>();
+            for (int i = 0, n = mCache.size(); i < n; i++) {
+                final AndroidPackageUsers pkg = mCache.valueAt(i);
+                if (pkg.mInstalledUsers.contains(userId)) {
+                    userPackages.put(mCache.keyAt(i), pkg.mPackage);
                 }
             }
-            try {
-                final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0, userId);
-                if (useCache && pi != null) {
-                    cachePackageInfo(packageName, userId, pi);
-                }
-                return pi;
-            } catch (RemoteException e) {
-                // Intentionally left empty.
-            }
-            return null;
+            return userPackages;
         }
 
         @Override
-        public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
-            return getPackageInfo(packageName, userId, true);
+        @Nullable
+        public AndroidPackage getPackageForUser(@NonNull final String packageName,
+                final int userId) {
+            final AndroidPackageUsers pkg = mCache.get(packageName);
+            if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
+                return pkg.mPackage;
+            }
+            try {
+                if (!mPackageManager.isPackageAvailable(packageName, userId)) {
+                    return null;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to check availability of package '" + packageName
+                        + "' for user " + userId, e);
+                return null;
+            }
+            return addPackageUser(packageName, userId);
+        }
+
+        @NonNull
+        private AndroidPackage addPackageUser(@NonNull final String packageName,
+                final int user) {
+            final AndroidPackage pkg = mPackageManagerInternal.getPackage(packageName);
+            if (pkg == null) {
+                Slog.w(TAG, "Android package for '" + packageName + "' could not be found;"
+                        + " continuing as if package was never added", new Throwable());
+                return null;
+            }
+            return addPackageUser(pkg, user);
+        }
+
+        @NonNull
+        private AndroidPackage addPackageUser(@NonNull final AndroidPackage pkg,
+                final int user) {
+            AndroidPackageUsers pkgUsers = mCache.get(pkg.getPackageName());
+            if (pkgUsers == null) {
+                pkgUsers = new AndroidPackageUsers(pkg);
+                mCache.put(pkg.getPackageName(), pkgUsers);
+            } else {
+                pkgUsers.mPackage = pkg;
+            }
+            pkgUsers.mInstalledUsers.add(user);
+            return pkgUsers.mPackage;
+        }
+
+
+        @NonNull
+        private void removePackageUser(@NonNull final String packageName, final int user) {
+            final AndroidPackageUsers pkgUsers = mCache.get(packageName);
+            if (pkgUsers == null) {
+                return;
+            }
+            removePackageUser(pkgUsers, user);
+        }
+
+        @NonNull
+        private void removePackageUser(@NonNull final AndroidPackageUsers pkg, final int user) {
+            pkg.mInstalledUsers.remove(user);
+            if (pkg.mInstalledUsers.isEmpty()) {
+                mCache.remove(pkg.mPackage.getPackageName());
+            }
+        }
+
+        @Nullable
+        public AndroidPackage onPackageAdded(@NonNull final String packageName, final int userId) {
+            return addPackageUser(packageName, userId);
+        }
+
+        @Nullable
+        public AndroidPackage onPackageUpdated(@NonNull final String packageName,
+                final int userId) {
+            return addPackageUser(packageName, userId);
+        }
+
+        public void onPackageRemoved(@NonNull final String packageName, final int userId) {
+            removePackageUser(packageName, userId);
+        }
+
+        @Override
+        public boolean isInstantApp(@NonNull final String packageName, final int userId) {
+            return mPackageManagerInternal.isInstantApp(packageName, userId);
         }
 
         @NonNull
@@ -1179,15 +1282,6 @@
         }
 
         @Override
-        public List<PackageInfo> getOverlayPackages(final int userId) {
-            final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);
-            for (final PackageInfo info : overlays) {
-                cachePackageInfo(info.packageName, userId, info);
-            }
-            return overlays;
-        }
-
-        @Override
         public String getConfigSignaturePackage() {
             final String[] pkgs = mPackageManagerInternal.getKnownPackageNames(
                     PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE,
@@ -1200,16 +1294,14 @@
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @NonNull String targetOverlayableName, int userId)
                 throws IOException {
-            PackageInfo packageInfo = getPackageInfo(packageName, userId);
+            final AndroidPackage packageInfo = getPackageForUser(packageName, userId);
             if (packageInfo == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
                 return apkAssets.getOverlayableInfo(targetOverlayableName);
             } finally {
                 if (apkAssets != null) {
@@ -1224,16 +1316,14 @@
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws IOException {
-            PackageInfo packageInfo = getPackageInfo(targetPackageName, userId);
+            AndroidPackage packageInfo = getPackageForUser(targetPackageName, userId);
             if (packageInfo == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
                 return apkAssets.definesOverlayable();
             } finally {
                 if (apkAssets != null) {
@@ -1250,35 +1340,10 @@
             mContext.enforceCallingOrSelfPermission(permission, message);
         }
 
-        public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
-                final int userId) {
-            final HashMap<String, PackageInfo> map = mCache.get(userId);
-            return map == null ? null : map.get(packageName);
-        }
-
-        public void cachePackageInfo(@NonNull final String packageName, final int userId,
-                @NonNull final PackageInfo pi) {
-            HashMap<String, PackageInfo> map = mCache.get(userId);
-            if (map == null) {
-                map = new HashMap<>();
-                mCache.put(userId, map);
-            }
-            map.put(packageName, pi);
-        }
-
-        public void forgetPackageInfo(@NonNull final String packageName, final int userId) {
-            final HashMap<String, PackageInfo> map = mCache.get(userId);
-            if (map == null) {
-                return;
-            }
-            map.remove(packageName);
-            if (map.isEmpty()) {
-                mCache.delete(userId);
-            }
-        }
-
         public void forgetAllPackageInfos(final int userId) {
-            mCache.delete(userId);
+            for (int i = 0, n = mCache.size(); i < n; i++) {
+                removePackageUser(mCache.valueAt(i), userId);
+            }
         }
 
         @Nullable
@@ -1292,19 +1357,12 @@
         }
 
         private static final String TAB1 = "    ";
-        private static final String TAB2 = TAB1 + TAB1;
 
         public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
-            pw.println("PackageInfo cache");
+            pw.println("AndroidPackage cache");
 
             if (!dumpState.isVerbose()) {
-                int count = 0;
-                final int n = mCache.size();
-                for (int i = 0; i < n; i++) {
-                    final int userId = mCache.keyAt(i);
-                    count += mCache.get(userId).size();
-                }
-                pw.println(TAB1 + count + " package(s)");
+                pw.println(TAB1 + mCache.size() + " package(s)");
                 return;
             }
 
@@ -1313,25 +1371,70 @@
                 return;
             }
 
-            final int n = mCache.size();
-            for (int i = 0; i < n; i++) {
-                final int userId = mCache.keyAt(i);
-                pw.println(TAB1 + "User " + userId);
-                final HashMap<String, PackageInfo> map = mCache.get(userId);
-                for (Map.Entry<String, PackageInfo> entry : map.entrySet()) {
-                    pw.println(TAB2 + entry.getKey() + ": " + entry.getValue());
-                }
+            for (int i = 0, n = mCache.size(); i < n; i++) {
+                final String packageName = mCache.keyAt(i);
+                final AndroidPackageUsers pkg = mCache.valueAt(i);
+                pw.print(TAB1 + packageName + ": " + pkg.mPackage + " users=");
+                pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
             }
         }
     }
 
+    private void updateTargetPackages(@Nullable PackageAndUser updatedTarget) {
+        if (updatedTarget != null) {
+            updateTargetPackages(Set.of(updatedTarget));
+        }
+    }
+
+    private void updateTargetPackages(@Nullable Set<PackageAndUser> updatedTargets) {
+        if (CollectionUtils.isEmpty(updatedTargets)) {
+            return;
+        }
+        persistSettings();
+        final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(updatedTargets);
+        FgThread.getHandler().post(() -> {
+            for (int i = 0, n = userTargets.size(); i < n; i++) {
+                final ArraySet<String> targets = userTargets.valueAt(i);
+                final int userId = userTargets.keyAt(i);
+
+                // Update the overlay paths in package manager.
+                final List<String> affectedPackages = updatePackageManager(targets, userId);
+                updateActivityManager(affectedPackages, userId);
+
+                // Overlays targeting shared libraries may cause more packages to need to be
+                // refreshed.
+                broadcastActionOverlayChanged(targets, userId);
+            }
+        });
+    }
+
+    @Nullable
+    private static SparseArray<ArraySet<String>> groupTargetsByUserId(
+            @Nullable final Set<PackageAndUser> targetsAndUsers) {
+        final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
+        CollectionUtils.forEach(targetsAndUsers, target -> {
+            ArraySet<String> targets = userTargets.get(target.userId);
+            if (targets == null) {
+                targets = new ArraySet<>();
+                userTargets.put(target.userId, targets);
+            }
+            targets.add(target.packageName);
+        });
+        return userTargets;
+    }
+
     // Helper methods to update other parts of the system or read/write
     // settings: these methods should never call into each other!
 
-    private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
+    private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
             final int userId) {
+        CollectionUtils.forEach(targetPackages,
+                target -> broadcastActionOverlayChanged(target, userId));
+    }
+
+    private static void broadcastActionOverlayChanged(String targetPackage, final int userId) {
         final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
-                Uri.fromParts("package", targetPackageName, null));
+                Uri.fromParts("package", targetPackage, null));
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         try {
             ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
@@ -1345,7 +1448,7 @@
      * Tell the activity manager to tell a set of packages to reload their
      * resources.
      */
-    private void updateActivityManager(List<String> targetPackageNames, final int userId) {
+    private void updateActivityManager(@NonNull List<String> targetPackageNames, final int userId) {
         final IActivityManager am = ActivityManager.getService();
         try {
             am.scheduleApplicationInfoChanged(targetPackageNames, userId);
@@ -1354,16 +1457,33 @@
         }
     }
 
-    private ArrayList<String> updatePackageManager(String targetPackageNames, final int userId) {
-        return updatePackageManager(Collections.singletonList(targetPackageNames), userId);
+    private void updateActivityManager(@NonNull SparseArray<List<String>> targetPackageNames) {
+        for (int i = 0, n = targetPackageNames.size(); i < n; i++) {
+            updateActivityManager(targetPackageNames.valueAt(i), targetPackageNames.keyAt(i));
+        }
+    }
+
+    @NonNull
+    private SparseArray<List<String>> updatePackageManager(@Nullable Set<PackageAndUser> targets) {
+        if (CollectionUtils.isEmpty(targets)) {
+            return new SparseArray<>();
+        }
+        final SparseArray<List<String>> affectedTargets = new SparseArray<>();
+        final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(targets);
+        for (int i = 0, n = userTargets.size(); i < n; i++) {
+            final int userId = userTargets.keyAt(i);
+            affectedTargets.put(userId, updatePackageManager(userTargets.valueAt(i), userId));
+        }
+        return affectedTargets;
     }
 
     /**
      * Updates the target packages' set of enabled overlays in PackageManager.
      * @return the package names of affected targets (a superset of
-     *         targetPackageNames: the target themserlves and shared libraries)
+     *         targetPackageNames: the target themselves and shared libraries)
      */
-    private ArrayList<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
+    @NonNull
+    private List<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
             final int userId) {
         try {
             traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c547c36..fb183f5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -28,26 +28,31 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.om.CriticalOverlayInfo;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.content.om.OverlayConfig;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Internal implementation of OverlayManagerService.
@@ -85,29 +90,42 @@
      * should either scrap the overlay manager's previous settings or merge the old
      * settings with the new.
      */
-    private boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth,
+    private boolean mustReinitializeOverlay(@NonNull final AndroidPackage theTruth,
             @Nullable final OverlayInfo oldSettings) {
         if (oldSettings == null) {
             return true;
         }
-        if (!Objects.equals(theTruth.overlayTarget, oldSettings.targetPackageName)) {
+        if (!Objects.equals(theTruth.getOverlayTarget(), oldSettings.targetPackageName)) {
             return true;
         }
-        if (!Objects.equals(theTruth.targetOverlayableName, oldSettings.targetOverlayableName)) {
+        if (!Objects.equals(theTruth.getOverlayTargetName(), oldSettings.targetOverlayableName)) {
             return true;
         }
-
-        boolean isMutable = isPackageConfiguredMutable(theTruth.packageName);
+        if (oldSettings.isFabricated) {
+            return true;
+        }
+        boolean isMutable = isPackageConfiguredMutable(theTruth);
         if (isMutable != oldSettings.isMutable) {
             return true;
         }
-
         // If an immutable overlay changes its configured enabled state, reinitialize the overlay.
-        if (!isMutable && isPackageConfiguredEnabled(theTruth.packageName)
-                != oldSettings.isEnabled()) {
+        if (!isMutable && isPackageConfiguredEnabled(theTruth) != oldSettings.isEnabled()) {
             return true;
         }
+        return false;
+    }
 
+    private boolean mustReinitializeOverlay(@NonNull final FabricatedOverlayInfo theTruth,
+            @Nullable final OverlayInfo oldSettings) {
+        if (oldSettings == null) {
+            return true;
+        }
+        if (!Objects.equals(theTruth.targetPackageName, oldSettings.targetPackageName)) {
+            return true;
+        }
+        if (!Objects.equals(theTruth.targetOverlayable, oldSettings.targetOverlayableName)) {
+            return true;
+        }
         return false;
     }
 
@@ -129,87 +147,45 @@
      * of two sets: the set of targets with currently active overlays, and the
      * set of targets that had, but no longer have, active overlays.
      */
-    ArrayList<String> updateOverlaysForUser(final int newUserId) {
+    @NonNull
+    ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) {
         if (DEBUG) {
             Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
         }
 
-        final Set<String> packagesToUpdateAssets = new ArraySet<>();
-        final ArrayMap<String, List<OverlayInfo>> tmp = mSettings.getOverlaysForUser(newUserId);
-        final int tmpSize = tmp.size();
-        final ArrayMap<String, OverlayInfo> storedOverlayInfos = new ArrayMap<>(tmpSize);
-        for (int i = 0; i < tmpSize; i++) {
-            final List<OverlayInfo> chunk = tmp.valueAt(i);
-            final int chunkSize = chunk.size();
-            for (int j = 0; j < chunkSize; j++) {
-                final OverlayInfo oi = chunk.get(j);
-                storedOverlayInfos.put(oi.packageName, oi);
-            }
-        }
+        // Remove the settings of all overlays that are no longer installed for this user.
+        final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>();
+        final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
+                newUserId);
+        CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
+                (info) -> !userPackages.containsKey(info.packageName), newUserId));
 
-        // Reset overlays if something critical like the target package name
-        // has changed
-        List<PackageInfo> overlayPackages = mPackageManager.getOverlayPackages(newUserId);
-        final int overlayPackagesSize = overlayPackages.size();
-        for (int i = 0; i < overlayPackagesSize; i++) {
-            final PackageInfo overlayPackage = overlayPackages.get(i);
-            final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
-
-            int priority = getPackageConfiguredPriority(overlayPackage.packageName);
-            if (mustReinitializeOverlay(overlayPackage, oi)) {
-                // if targetPackageName has changed the package that *used* to
-                // be the target must also update its assets
-                if (oi != null) {
-                    packagesToUpdateAssets.add(oi.targetPackageName);
-                }
-
-                mSettings.init(overlayPackage.packageName, newUserId,
-                        overlayPackage.overlayTarget,
-                        overlayPackage.targetOverlayableName,
-                        overlayPackage.applicationInfo.getBaseCodePath(),
-                        isPackageConfiguredMutable(overlayPackage.packageName),
-                        isPackageConfiguredEnabled(overlayPackage.packageName),
-                        priority, overlayPackage.overlayCategory);
-            } else if (priority != oi.priority) {
-                mSettings.setPriority(overlayPackage.packageName, newUserId, priority);
-                packagesToUpdateAssets.add(oi.targetPackageName);
-            }
-
-            storedOverlayInfos.remove(overlayPackage.packageName);
-        }
-
-        // any OverlayInfo left in storedOverlayInfos is no longer
-        // installed and should be removed
-        final int storedOverlayInfosSize = storedOverlayInfos.size();
-        for (int i = 0; i < storedOverlayInfosSize; i++) {
-            final OverlayInfo oi = storedOverlayInfos.valueAt(i);
-            mSettings.remove(oi.packageName, oi.userId);
-            removeIdmapIfPossible(oi);
-            packagesToUpdateAssets.add(oi.targetPackageName);
-        }
-
-        // make sure every overlay's state is up-to-date; this needs to happen
-        // after old overlays have been removed, or we risk removing a
-        // legitimate idmap file if a new overlay package has the same apk path
-        // as the removed overlay package used to have
-        for (int i = 0; i < overlayPackagesSize; i++) {
-            final PackageInfo overlayPackage = overlayPackages.get(i);
+        // Update the state of all installed packages containing overlays, and initialize new
+        // overlays that are not currently in the settings.
+        for (int i = 0, n = userPackages.size(); i < n; i++) {
+            final AndroidPackage pkg = userPackages.valueAt(i);
             try {
-                updateState(overlayPackage.overlayTarget, overlayPackage.packageName,
-                        newUserId, 0);
-            } catch (OverlayManagerSettings.BadKeyException e) {
-                Slog.e(TAG, "failed to update settings", e);
-                mSettings.remove(overlayPackage.packageName, newUserId);
+                CollectionUtils.addAll(updatedTargets,
+                        updatePackageOverlays(pkg, newUserId, 0 /* flags */));
+
+                // When a new user is switched to for the first time, package manager must be
+                // informed of the overlay paths for all packages installed in the user.
+                updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+            } catch (OperationFailedException e) {
+                Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
+                        + "' for user " + newUserId + "", e);
             }
-            packagesToUpdateAssets.add(overlayPackage.overlayTarget);
         }
 
-        // remove target packages that are not installed
-        final Iterator<String> iter = packagesToUpdateAssets.iterator();
-        while (iter.hasNext()) {
-            String targetPackageName = iter.next();
-            if (mPackageManager.getPackageInfo(targetPackageName, newUserId) == null) {
-                iter.remove();
+        // Update the state of all fabricated overlays, and initialize fabricated overlays in the
+        // new user.
+        for (final FabricatedOverlayInfo info : getFabricatedOverlayInfos()) {
+            try {
+                CollectionUtils.addAll(updatedTargets, registerFabricatedOverlay(
+                        info, newUserId));
+            } catch (OperationFailedException e) {
+                Slog.e(TAG, "failed to initialize fabricated overlay of '" + info.path
+                        + "' for user " + newUserId + "", e);
             }
         }
 
@@ -232,14 +208,20 @@
         // Enable the default overlay if its category does not have a single overlay enabled.
         for (final String defaultOverlay : mDefaultOverlays) {
             try {
-                final OverlayInfo oi = mSettings.getOverlayInfo(defaultOverlay, newUserId);
+                // OverlayConfig is the new preferred way to enable overlays by default. This legacy
+                // default enabled method was created before overlays could have a name specified.
+                // Only allow enabling overlays without a name using this mechanism.
+                final OverlayIdentifier overlay = new OverlayIdentifier(defaultOverlay);
+
+                final OverlayInfo oi = mSettings.getOverlayInfo(overlay, newUserId);
                 if (!enabledCategories.contains(oi.category)) {
                     Slog.w(TAG, "Enabling default overlay '" + defaultOverlay + "' for target '"
                             + oi.targetPackageName + "' in category '" + oi.category + "' for user "
                             + newUserId);
-                    mSettings.setEnabled(oi.packageName, newUserId, true);
-                    if (updateState(oi.targetPackageName, oi.packageName, newUserId, 0)) {
-                        packagesToUpdateAssets.add(oi.targetPackageName);
+                    mSettings.setEnabled(overlay, newUserId, true);
+                    if (updateState(oi, newUserId, 0)) {
+                        CollectionUtils.add(updatedTargets,
+                                new PackageAndUser(oi.targetPackageName, oi.userId));
                     }
                 }
             } catch (OverlayManagerSettings.BadKeyException e) {
@@ -248,7 +230,8 @@
             }
         }
 
-        return new ArrayList<>(packagesToUpdateAssets);
+        cleanStaleResourceCache();
+        return updatedTargets;
     }
 
     void onUserRemoved(final int userId) {
@@ -258,236 +241,151 @@
         mSettings.removeUser(userId);
     }
 
-    Optional<PackageAndUser> onTargetPackageAdded(@NonNull final String packageName,
+    @NonNull
+    Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
-    Optional<PackageAndUser> onTargetPackageChanged(@NonNull final String packageName,
+    @NonNull
+    Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
-    Optional<PackageAndUser> onTargetPackageReplacing(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId="
-                    + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
-    }
-
-    Optional<PackageAndUser> onTargetPackageReplaced(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
-    }
-
-    Optional<PackageAndUser> onTargetPackageRemoved(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
-    }
-
-    /**
-     * Update the state of any overlays for this target.
-     */
-    private Optional<PackageAndUser> updateAndRefreshOverlaysForTarget(
-            @NonNull final String targetPackageName, final int userId, final int flags)
+    @NonNull
+    Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId)
             throws OperationFailedException {
-        final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName,
-                userId);
+        return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
+    }
 
-        // Update the state for any overlay that targets this package.
+    @NonNull
+    Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId)
+            throws OperationFailedException {
+        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
+    }
+
+    @NonNull
+    Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
+        }
+        // Update the state of all overlays that target this package.
+        final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+
+        // Remove all the overlays this package declares.
+        return CollectionUtils.addAll(targets,
+                removeOverlaysForUser(oi -> pkgName.equals(oi.packageName), userId));
+    }
+
+    @NonNull
+    private Set<PackageAndUser> removeOverlaysForUser(
+            @NonNull final Predicate<OverlayInfo> condition, final int userId) {
+        final List<OverlayInfo> overlays = mSettings.removeIf(
+                io -> userId == io.userId && condition.test(io) );
+        Set<PackageAndUser> targets = Collections.emptySet();
+        for (int i = 0, n = overlays.size(); i < n; i++) {
+            final OverlayInfo info = overlays.get(i);
+            targets = CollectionUtils.add(targets,
+                    new PackageAndUser(info.targetPackageName, userId));
+
+            // Remove the idmap if the overlay is no longer installed for any user.
+            removeIdmapIfPossible(info);
+        }
+        return targets;
+    }
+
+    @NonNull
+    private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage,
+            final int userId, final int flags) {
         boolean modified = false;
-        for (final OverlayInfo oi : targetOverlays) {
-            final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName,
-                    userId);
-            if (overlayPackage == null) {
-                modified |= mSettings.remove(oi.packageName, oi.userId);
-                removeIdmapIfPossible(oi);
-            } else {
-                try {
-                    modified |= updateState(targetPackageName, oi.packageName, userId, flags);
-                } catch (OverlayManagerSettings.BadKeyException e) {
-                    Slog.e(TAG, "failed to update settings", e);
-                    modified |= mSettings.remove(oi.packageName, userId);
-                }
+        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId);
+        for (int i = 0, n = overlays.size(); i < n; i++) {
+            final OverlayInfo oi = overlays.get(i);
+            try {
+                modified |= updateState(oi, userId, flags);
+            } catch (OverlayManagerSettings.BadKeyException e) {
+                Slog.e(TAG, "failed to update settings", e);
+                modified |= mSettings.remove(oi.getOverlayIdentifier(), userId);
             }
         }
-
         if (!modified) {
-            // Update the overlay paths of the target within package manager if necessary.
-            final List<String> enabledOverlayPaths = new ArrayList<>(targetOverlays.size());
-
-            // Framework overlays are first in the overlay paths of a package within PackageManager.
-            for (final OverlayInfo oi : mSettings.getOverlaysForTarget("android", userId)) {
-                if (oi.isEnabled()) {
-                    enabledOverlayPaths.add(oi.baseCodePath);
-                }
-            }
-
-            for (final OverlayInfo oi : targetOverlays) {
-                if (oi.isEnabled()) {
-                    enabledOverlayPaths.add(oi.baseCodePath);
-                }
-            }
-
-            // TODO(): Use getEnabledOverlayPaths(userId, targetPackageName) instead of
-            // resourceDirs if in the future resourceDirs contains APKs other than overlays
-            PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
-            ApplicationInfo appInfo = packageInfo == null ? null : packageInfo.applicationInfo;
-            String[] resourceDirs = appInfo == null ? null : appInfo.resourceDirs;
-
-            // If the lists aren't the same length, the enabled overlays have changed
-            if (ArrayUtils.size(resourceDirs) != enabledOverlayPaths.size()) {
-                modified = true;
-            } else if (resourceDirs != null) {
-                // If any element isn't equal, an overlay or the order of overlays has changed
-                for (int index = 0; index < resourceDirs.length; index++) {
-                    if (!resourceDirs[index].equals(enabledOverlayPaths.get(index))) {
-                        modified = true;
-                        break;
-                    }
-                }
-            }
+            return Collections.emptySet();
         }
-
-        if (modified) {
-            return Optional.of(new PackageAndUser(targetPackageName, userId));
-        }
-        return Optional.empty();
+        return Set.of(new PackageAndUser(targetPackage, userId));
     }
 
-    Optional<PackageAndUser> onOverlayPackageAdded(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId);
+    @NonNull
+    private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg,
+            final int userId, final int flags) throws OperationFailedException {
+        if (pkg.getOverlayTarget() == null) {
+            // This package does not have overlays declared in its manifest.
+            return Collections.emptySet();
         }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found");
-            return onOverlayPackageRemoved(packageName, userId);
-        }
-
-        mSettings.init(packageName, userId, overlayPackage.overlayTarget,
-                overlayPackage.targetOverlayableName,
-                overlayPackage.applicationInfo.getBaseCodePath(),
-                isPackageConfiguredMutable(overlayPackage.packageName),
-                isPackageConfiguredEnabled(overlayPackage.packageName),
-                getPackageConfiguredPriority(overlayPackage.packageName),
-                overlayPackage.overlayCategory);
+        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName());
+        final int priority = getPackageConfiguredPriority(pkg);
         try {
-            if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
-                return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            mSettings.remove(packageName, userId);
-            throw new OperationFailedException("failed to update settings", e);
-        }
-    }
+            OverlayInfo currentInfo = mSettings.getNullableOverlayInfo(overlay, userId);
+            if (mustReinitializeOverlay(pkg, currentInfo)) {
+                if (currentInfo != null) {
+                    // If the targetPackageName has changed, the package that *used* to
+                    // be the target must also update its assets.
+                    updatedTargets = CollectionUtils.add(updatedTargets,
+                            new PackageAndUser(currentInfo.targetPackageName, userId));
+                }
 
-    Optional<PackageAndUser> onOverlayPackageChanged(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
-        }
-
-        try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-            if (updateState(oi.targetPackageName, packageName, userId, 0)) {
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+                currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
+                        pkg.getOverlayTargetName(), pkg.getBaseApkPath(),
+                        isPackageConfiguredMutable(pkg),
+                        isPackageConfiguredEnabled(pkg),
+                        getPackageConfiguredPriority(pkg), pkg.getOverlayCategory(),
+                        false);
+            } else if (priority != currentInfo.priority) {
+                // Changing the priority of an overlay does not cause its settings to be
+                // reinitialized. Reorder the overlay and update its target package.
+                mSettings.setPriority(overlay, userId, priority);
+                updatedTargets = CollectionUtils.add(updatedTargets,
+                        new PackageAndUser(currentInfo.targetPackageName, userId));
             }
-            return Optional.empty();
+
+            // Update the enabled state of the overlay.
+            if (updateState(currentInfo, userId, flags)) {
+                updatedTargets = CollectionUtils.add(updatedTargets,
+                        new PackageAndUser(currentInfo.targetPackageName, userId));
+            }
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
         }
+        return updatedTargets;
     }
 
-    Optional<PackageAndUser> onOverlayPackageReplacing(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
+    @NonNull
+    private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName,
+            final int userId, final int flags) throws OperationFailedException {
         if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId="
-                    + userId);
+            Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId);
         }
 
-        try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-            if (updateState(oi.targetPackageName, packageName, userId,
-                        FLAG_OVERLAY_IS_BEING_REPLACED)) {
-                removeIdmapIfPossible(oi);
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            throw new OperationFailedException("failed to update settings", e);
-        }
-    }
+        // Update the state of overlays that target this package.
+        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        updatedTargets = CollectionUtils.addAll(updatedTargets,
+                updateOverlaysForTarget(pkgName, userId, flags));
 
-    Optional<PackageAndUser> onOverlayPackageReplaced(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId="
-                    + userId);
-        }
-
-        final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
+        // Realign the overlay settings with PackageManager's view of the package.
+        final AndroidPackage pkg = mPackageManager.getPackageForUser(pkgName, userId);
         if (pkg == null) {
-            Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found");
-            return onOverlayPackageRemoved(packageName, userId);
+            return onPackageRemoved(pkgName, userId);
         }
 
-        try {
-            final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
-            if (mustReinitializeOverlay(pkg, oldOi)) {
-                mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName,
-                        pkg.applicationInfo.getBaseCodePath(),
-                        isPackageConfiguredMutable(pkg.packageName),
-                        isPackageConfiguredEnabled(pkg.packageName),
-                        getPackageConfiguredPriority(pkg.packageName), pkg.overlayCategory);
-            }
-
-            if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
-                return Optional.of(new PackageAndUser(pkg.overlayTarget, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            throw new OperationFailedException("failed to update settings", e);
-        }
+        // Update the state of the overlays this package declares in its manifest.
+        updatedTargets = CollectionUtils.addAll(updatedTargets,
+                updatePackageOverlays(pkg, userId, flags));
+        return updatedTargets;
     }
 
-    Optional<PackageAndUser> onOverlayPackageRemoved(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        try {
-            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
-            if (mSettings.remove(packageName, userId)) {
-                removeIdmapIfPossible(overlayInfo);
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            throw new OperationFailedException("failed to remove overlay", e);
-        }
-    }
-
-    OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
+    OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier packageName, final int userId) {
         try {
             return mSettings.getOverlayInfo(packageName, userId);
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -504,94 +402,78 @@
         return mSettings.getOverlaysForUser(userId);
     }
 
-    Optional<PackageAndUser> setEnabled(@NonNull final String packageName, final boolean enable,
-            final int userId) throws OperationFailedException {
+    @NonNull
+    Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+            final boolean enable, final int userId) throws OperationFailedException {
         if (DEBUG) {
-            Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
-                        packageName, enable, userId));
-        }
-
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(
-                    String.format("failed to find overlay package %s for user %d",
-                        packageName, userId));
+            Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
+                    overlay, enable, userId));
         }
 
         try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+            final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId);
             if (!oi.isMutable) {
                 // Ignore immutable overlays.
                 throw new OperationFailedException(
                         "cannot enable immutable overlay packages in runtime");
             }
 
-            boolean modified = mSettings.setEnabled(packageName, userId, enable);
-            modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
+            boolean modified = mSettings.setEnabled(overlay, userId, enable);
+            modified |= updateState(oi, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
             }
-            return Optional.empty();
+            return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    Optional<PackageAndUser> setEnabledExclusive(@NonNull final String packageName,
+    Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
             boolean withinCategory, final int userId) throws OperationFailedException {
         if (DEBUG) {
-            Slog.d(TAG, String.format("setEnabledExclusive packageName=%s"
-                    + " withinCategory=%s userId=%d", packageName, withinCategory, userId));
-        }
-
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+            Slog.d(TAG, String.format("setEnabledExclusive overlay=%s"
+                    + " withinCategory=%s userId=%d", overlay, withinCategory, userId));
         }
 
         try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-            final String targetPackageName = oi.targetPackageName;
+            final OverlayInfo enabledInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!enabledInfo.isMutable) {
+                throw new OperationFailedException(
+                        "cannot enable immutable overlay packages in runtime");
+            }
 
-            List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId);
+            // Remove the overlay to have enabled from the list of overlays to disable.
+            List<OverlayInfo> allOverlays = getOverlayInfosForTarget(enabledInfo.targetPackageName,
+                    userId);
+            allOverlays.remove(enabledInfo);
 
             boolean modified = false;
-
-            // Disable all other overlays.
-            allOverlays.remove(oi);
             for (int i = 0; i < allOverlays.size(); i++) {
                 final OverlayInfo disabledInfo = allOverlays.get(i);
-                final String disabledOverlayPackageName = disabledInfo.packageName;
-                final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo(
-                        disabledOverlayPackageName, userId);
-                if (disabledOverlayPackageInfo == null) {
-                    modified |= mSettings.remove(disabledOverlayPackageName, userId);
-                    continue;
-                }
-
+                final OverlayIdentifier disabledOverlay = disabledInfo.getOverlayIdentifier();
                 if (!disabledInfo.isMutable) {
                     // Don't touch immutable overlays.
                     continue;
                 }
-                if (withinCategory && !Objects.equals(disabledOverlayPackageInfo.overlayCategory,
-                        oi.category)) {
+                if (withinCategory && !Objects.equals(disabledInfo.category,
+                        enabledInfo.category)) {
                     // Don't touch overlays from other categories.
                     continue;
                 }
 
                 // Disable the overlay.
-                modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
-                modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0);
+                modified |= mSettings.setEnabled(disabledOverlay, userId, false);
+                modified |= updateState(disabledInfo, userId, 0);
             }
 
             // Enable the selected overlay.
-            modified |= mSettings.setEnabled(packageName, userId, true);
-            modified |= updateState(targetPackageName, packageName, userId, 0);
+            modified |= mSettings.setEnabled(overlay, userId, true);
+            modified |= updateState(enabledInfo, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(targetPackageName, userId));
+                return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -599,87 +481,200 @@
         }
     }
 
-    private boolean isPackageConfiguredMutable(@NonNull final String packageName) {
-        return mOverlayConfig.isMutable(packageName);
-    }
-
-    private int getPackageConfiguredPriority(@NonNull final String packageName) {
-        return mOverlayConfig.getPriority(packageName);
-    }
-
-    private boolean isPackageConfiguredEnabled(@NonNull final String packageName) {
-        return mOverlayConfig.isEnabled(packageName);
-    }
-
-    Optional<PackageAndUser> setPriority(@NonNull final String packageName,
-            @NonNull final String newParentPackageName, final int userId)
+    @NonNull
+    Set<PackageAndUser> registerFabricatedOverlay(
+            @NonNull final FabricatedOverlayInternal overlay)
             throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName="
-                    + newParentPackageName + " userId=" + userId);
+        if (ParsingPackageUtils.validateName(overlay.overlayName,
+                false /* requireSeparator */, true /* requireFilename */) != null) {
+            throw new OperationFailedException(
+                    "overlay name can only consist of alphanumeric characters, '_', and '.'");
         }
 
-        if (!isPackageConfiguredMutable(packageName)) {
-            throw new OperationFailedException(String.format(
-                        "overlay package %s user %d is not updatable", packageName, userId));
+        final FabricatedOverlayInfo info = mIdmapManager.createFabricatedOverlay(overlay);
+        if (info == null) {
+            throw new OperationFailedException("failed to create fabricated overlay");
         }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        for (int userId : mSettings.getUsers()) {
+            updatedTargets.addAll(registerFabricatedOverlay(info, userId));
         }
-
-        if (mSettings.setPriority(packageName, newParentPackageName, userId)) {
-            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-        }
-        return Optional.empty();
+        return updatedTargets;
     }
 
-    Optional<PackageAndUser> setHighestPriority(@NonNull final String packageName,
+    @NonNull
+    private Set<PackageAndUser> registerFabricatedOverlay(
+            @NonNull final FabricatedOverlayInfo info, int userId)
+            throws OperationFailedException {
+        final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
+                info.packageName, info.overlayName);
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
+        if (oi != null) {
+            if (!oi.isFabricated) {
+                throw new OperationFailedException("non-fabricated overlay with name '" +
+                        oi.overlayName + "' already present in '" + oi.packageName + "'");
+            }
+        }
+        try {
+            if (mustReinitializeOverlay(info, oi)) {
+                if (oi != null) {
+                    // If the fabricated overlay changes its target package, update the previous
+                    // target package so it no longer is overlaid.
+                    updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                }
+                oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
+                        info.targetOverlayable, info.path, true, false,
+                        OverlayConfig.DEFAULT_PRIORITY, null, true);
+            } else {
+                // The only non-critical part of the info that will change is path to the fabricated
+                // overlay.
+                mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
+            }
+            if (updateState(oi, userId, 0)) {
+                updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+            }
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
+
+        return updatedTargets;
+    }
+
+    @NonNull
+    Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        for (int userId : mSettings.getUsers()) {
+            updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
+        }
+        return updatedTargets;
+    }
+
+    @NonNull
+    private Set<PackageAndUser> unregisterFabricatedOverlay(
+            @NonNull final OverlayIdentifier overlay, int userId) {
+        final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
+        if (oi != null) {
+            mSettings.remove(overlay, userId);
+            if (oi.isEnabled()) {
+                // Removing a fabricated overlay only changes the overlay path of a package if it is
+                // currently enabled.
+                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+            }
+        }
+        return Set.of();
+    }
+
+
+    private void cleanStaleResourceCache() {
+        // Clean up fabricated overlays that are no longer registered in any user.
+        final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+        for (final FabricatedOverlayInfo info : mIdmapManager.getFabricatedOverlayInfos()) {
+            if (!fabricatedPaths.contains(info.path)) {
+                mIdmapManager.deleteFabricatedOverlay(info.path);
+            }
+        }
+    }
+
+    /**
+     * Retrieves information about the fabricated overlays still in use.
+     * @return
+     */
+    @NonNull
+    private List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+        // Filter out stale fabricated overlays.
+        final ArrayList<FabricatedOverlayInfo> infos = new ArrayList<>(
+                mIdmapManager.getFabricatedOverlayInfos());
+        infos.removeIf(info -> !fabricatedPaths.contains(info.path));
+        return infos;
+    }
+
+    private boolean isPackageConfiguredMutable(@NonNull final AndroidPackage overlay) {
+        // TODO(162841629): Support overlay name in OverlayConfig
+        return mOverlayConfig.isMutable(overlay.getPackageName());
+    }
+
+    private int getPackageConfiguredPriority(@NonNull final AndroidPackage overlay) {
+        // TODO(162841629): Support overlay name in OverlayConfig
+        return mOverlayConfig.getPriority(overlay.getPackageName());
+    }
+
+    private boolean isPackageConfiguredEnabled(@NonNull final AndroidPackage overlay) {
+        // TODO(162841629): Support overlay name in OverlayConfig
+        return mOverlayConfig.isEnabled(overlay.getPackageName());
+    }
+
+    Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay,
+            @NonNull final OverlayIdentifier newParentOverlay, final int userId)
+            throws OperationFailedException {
+        try {
+            if (DEBUG) {
+                Slog.d(TAG, "setPriority overlay=" + overlay + " newParentOverlay="
+                        + newParentOverlay + " userId=" + userId);
+            }
+
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!overlayInfo.isMutable) {
+                // Ignore immutable overlays.
+                throw new OperationFailedException(
+                        "cannot change priority of an immutable overlay package at runtime");
+            }
+
+            if (mSettings.setPriority(overlay, newParentOverlay, userId)) {
+                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+            }
+            return Optional.empty();
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
+    }
+
+    Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
-        }
+        try{
+            if (DEBUG) {
+                Slog.d(TAG, "setHighestPriority overlay=" + overlay + " userId=" + userId);
+            }
 
-        if (!isPackageConfiguredMutable(packageName)) {
-            throw new OperationFailedException(String.format(
-                        "overlay package %s user %d is not updatable", packageName, userId));
-        }
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!overlayInfo.isMutable) {
+                // Ignore immutable overlays.
+                throw new OperationFailedException(
+                        "cannot change priority of an immutable overlay package at runtime");
+            }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+            if (mSettings.setHighestPriority(overlay, userId)) {
+                return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+            }
+            return Set.of();
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
         }
-
-        if (mSettings.setHighestPriority(packageName, userId)) {
-            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-        }
-        return Optional.empty();
     }
 
-    Optional<PackageAndUser> setLowestPriority(@NonNull final String packageName, final int userId)
-            throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
-        }
+    Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay,
+            final int userId) throws OperationFailedException {
+        try{
+            if (DEBUG) {
+                Slog.d(TAG, "setLowestPriority packageName=" + overlay + " userId=" + userId);
+            }
 
-        if (!isPackageConfiguredMutable(packageName)) {
-            throw new OperationFailedException(String.format(
-                        "overlay package %s user %d is not updatable", packageName, userId));
-        }
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!overlayInfo.isMutable) {
+                // Ignore immutable overlays.
+                throw new OperationFailedException(
+                        "cannot change priority of an immutable overlay package at runtime");
+            }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+            if (mSettings.setLowestPriority(overlay, userId)) {
+                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+            }
+            return Optional.empty();
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
         }
-
-        if (mSettings.setLowestPriority(packageName, userId)) {
-            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-        }
-        return Optional.empty();
     }
 
     void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
@@ -693,9 +688,14 @@
         return mDefaultOverlays;
     }
 
-    void removeIdmapForOverlay(String packageName, int userId) {
-        final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-        removeIdmapIfPossible(oi);
+    void removeIdmapForOverlay(OverlayIdentifier overlay, int userId)
+            throws OperationFailedException {
+        try {
+            final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId);
+            removeIdmapIfPossible(oi);
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
     }
 
     OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
@@ -709,7 +709,11 @@
             if (!oi.isEnabled()) {
                 continue;
             }
-            paths.addApkPath(oi.baseCodePath);
+            if (oi.isFabricated()) {
+                paths.addNonApkPath(oi.baseCodePath);
+            } else {
+                paths.addApkPath(oi.baseCodePath);
+            }
         }
         return paths.build();
     }
@@ -717,49 +721,53 @@
     /**
      * Returns true if the settings/state was modified, false otherwise.
      */
-    private boolean updateState(@NonNull final String targetPackageName,
-            @NonNull final String overlayPackageName, final int userId, final int flags)
-            throws OverlayManagerSettings.BadKeyException {
+    private boolean updateState(@NonNull final CriticalOverlayInfo info,
+            final int userId, final int flags) throws OverlayManagerSettings.BadKeyException {
+        final OverlayIdentifier overlay = info.getOverlayIdentifier();
+        final AndroidPackage targetPackage = mPackageManager.getPackageForUser(
+                info.getTargetPackageName(), userId);
+        final AndroidPackage overlayPackage = mPackageManager.getPackageForUser(
+                info.getPackageName(), userId);
 
-        final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
-                userId);
+        boolean modified = false;
+        if (overlayPackage == null) {
+            removeIdmapIfPossible(mSettings.getOverlayInfo(overlay, userId));
+            return mSettings.remove(overlay, userId);
+        }
+
+        modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory());
+        if (!info.isFabricated()) {
+            modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getBaseApkPath());
+        }
 
         // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
         // layers.
-        boolean modified = false;
-        if (targetPackage != null && overlayPackage != null
-                && !("android".equals(targetPackageName)
-                    && !isPackageConfiguredMutable(overlayPackageName))) {
-            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+        final OverlayInfo updatedOverlayInfo = mSettings.getOverlayInfo(overlay, userId);
+        if (targetPackage != null && !("android".equals(info.getTargetPackageName())
+                && !isPackageConfiguredMutable(overlayPackage))) {
+            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage,
+                    updatedOverlayInfo.baseCodePath, overlay.getOverlayName(), userId);
         }
 
-        if (overlayPackage != null) {
-            modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
-                    overlayPackage.applicationInfo.getBaseCodePath());
-            modified |= mSettings.setCategory(overlayPackageName, userId,
-                    overlayPackage.overlayCategory);
-        }
-
-        final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
-        final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
+        final @OverlayInfo.State int currentState = mSettings.getState(overlay, userId);
+        final @OverlayInfo.State int newState = calculateNewState(updatedOverlayInfo, targetPackage,
                 userId, flags);
         if (currentState != newState) {
             if (DEBUG) {
                 Slog.d(TAG, String.format("%s:%d: %s -> %s",
-                        overlayPackageName, userId,
+                        overlay, userId,
                         OverlayInfo.stateToString(currentState),
                         OverlayInfo.stateToString(newState)));
             }
-            modified |= mSettings.setState(overlayPackageName, userId, newState);
+            modified |= mSettings.setState(overlay, userId, newState);
         }
+
         return modified;
     }
 
-    private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
-            @Nullable final PackageInfo overlayPackage, final int userId, final int flags)
+    private @OverlayInfo.State int calculateNewState(@NonNull final OverlayInfo info,
+            @Nullable final AndroidPackage targetPackage, final int userId, final int flags)
             throws OverlayManagerSettings.BadKeyException {
-
         if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
             return STATE_TARGET_IS_BEING_REPLACED;
         }
@@ -768,20 +776,15 @@
             return STATE_OVERLAY_IS_BEING_REPLACED;
         }
 
-        // assert expectation on overlay package: can only be null if the flags are used
-        if (DEBUG && overlayPackage == null) {
-            throw new IllegalArgumentException("null overlay package not compatible with no flags");
-        }
-
         if (targetPackage == null) {
             return STATE_MISSING_TARGET;
         }
 
-        if (!mIdmapManager.idmapExists(overlayPackage, userId)) {
+        if (!mIdmapManager.idmapExists(info)) {
             return STATE_NO_IDMAP;
         }
 
-        final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
+        final boolean enabled = mSettings.getEnabled(info.getOverlayIdentifier(), userId);
         return enabled ? STATE_ENABLED : STATE_DISABLED;
     }
 
@@ -810,7 +813,7 @@
         final int[] userIds = mSettings.getUsers();
         for (int userId : userIds) {
             try {
-                final OverlayInfo tmp = mSettings.getOverlayInfo(oi.packageName, userId);
+                final OverlayInfo tmp = mSettings.getOverlayInfo(oi.getOverlayIdentifier(), userId);
                 if (tmp != null && tmp.isEnabled()) {
                     // someone is still using the idmap file -> we cannot remove it
                     return;
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 0613dff..e3e0906 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -21,31 +21,32 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 /**
@@ -68,34 +69,45 @@
      */
     private final ArrayList<SettingsItem> mItems = new ArrayList<>();
 
-    void init(@NonNull final String packageName, final int userId,
+    @NonNull
+    OverlayInfo init(@NonNull final OverlayIdentifier overlay, final int userId,
             @NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
             @NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority,
-            @Nullable String overlayCategory) {
-        remove(packageName, userId);
-        insert(new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority,
-                overlayCategory));
+            @Nullable String overlayCategory, boolean isFabricated) {
+        remove(overlay, userId);
+        final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName,
+                targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled,
+                isMutable, priority, overlayCategory, isFabricated);
+        insert(item);
+        return item.getOverlayInfo();
     }
 
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean remove(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
+    boolean remove(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
         if (idx < 0) {
             return false;
         }
-
         mItems.remove(idx);
         return true;
     }
 
-    @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
+    @NonNull OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId)
             throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
+        }
+        return mItems.get(idx).getOverlayInfo();
+    }
+
+    @Nullable
+    OverlayInfo getNullableOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
+        if (idx < 0) {
+            return null;
         }
         return mItems.get(idx).getOverlayInfo();
     }
@@ -103,28 +115,29 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setBaseCodePath(@NonNull final String packageName, final int userId,
+    boolean setBaseCodePath(@NonNull final OverlayIdentifier overlay, final int userId,
             @NonNull final String path) throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setBaseCodePath(path);
     }
 
-    boolean setCategory(@NonNull final String packageName, final int userId,
+    boolean setCategory(@NonNull final OverlayIdentifier overlay, final int userId,
             @Nullable String category) throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setCategory(category);
     }
 
-    boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException {
-        final int idx = select(packageName, userId);
+    boolean getEnabled(@NonNull final OverlayIdentifier overlay, final int userId)
+            throws BadKeyException {
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).isEnabled();
     }
@@ -132,20 +145,20 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
-            throws BadKeyException {
-        final int idx = select(packageName, userId);
+    boolean setEnabled(@NonNull final OverlayIdentifier overlay, final int userId,
+            final boolean enable) throws BadKeyException {
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setEnabled(enable);
     }
 
-    @OverlayInfo.State int getState(@NonNull final String packageName, final int userId)
+    @OverlayInfo.State int getState(@NonNull final OverlayIdentifier overlay, final int userId)
             throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).getState();
     }
@@ -153,11 +166,11 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setState(@NonNull final String packageName, final int userId,
+    boolean setState(@NonNull final OverlayIdentifier overlay, final int userId,
             final @OverlayInfo.State int state) throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setState(state);
     }
@@ -166,53 +179,82 @@
             final int userId) {
         // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
         // ignored in OverlayManagerService.
-        return selectWhereTarget(targetPackageName, userId)
-                .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
-                .map(SettingsItem::getOverlayInfo)
-                .collect(Collectors.toList());
+        final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId);
+        items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
+        return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
     }
 
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
         // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
         // ignored in OverlayManagerService.
-        return selectWhereUser(userId)
-                .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
-                .map(SettingsItem::getOverlayInfo)
-                .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
-                        Collectors.toList()));
+        final List<SettingsItem> items = selectWhereUser(userId);
+        items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
+
+        final ArrayMap<String, List<OverlayInfo>> targetInfos = new ArrayMap<>();
+        for (int i = 0, n = items.size(); i < n; i++) {
+            final SettingsItem item = items.get(i);
+            targetInfos.computeIfAbsent(item.mTargetPackageName, (String) -> new ArrayList<>())
+                    .add(item.getOverlayInfo());
+        }
+        return targetInfos;
+    }
+
+    Set<String> getAllBaseCodePaths() {
+        final Set<String> paths = new ArraySet<>();
+        mItems.forEach(item -> paths.add(item.mBaseCodePath));
+        return paths;
+    }
+
+    @NonNull
+    List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) {
+        return removeIf(info -> (predicate.test(info) && info.userId == userId));
+    }
+
+    @NonNull
+    List<OverlayInfo> removeIf(final @NonNull Predicate<OverlayInfo> predicate) {
+        List<OverlayInfo> removed = null;
+        for (int i = mItems.size() - 1; i >= 0; i--) {
+            final OverlayInfo info = mItems.get(i).getOverlayInfo();
+            if (predicate.test(info)) {
+                mItems.remove(i);
+                removed = CollectionUtils.add(removed, info);
+            }
+        }
+        return CollectionUtils.emptyIfNull(removed);
     }
 
     int[] getUsers() {
         return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray();
     }
 
+    private static boolean isImmutableFrameworkOverlay(@NonNull SettingsItem item) {
+        return !item.isMutable() && "android".equals(item.getTargetPackageName());
+    }
+
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
     boolean removeUser(final int userId) {
-        boolean removed = false;
-        for (int i = 0; i < mItems.size(); i++) {
-            final SettingsItem item = mItems.get(i);
+        return mItems.removeIf(item -> {
             if (item.getUserId() == userId) {
                 if (DEBUG) {
-                    Slog.d(TAG, "Removing overlay " + item.mPackageName + " for user " + userId
+                    Slog.d(TAG, "Removing overlay " + item.mOverlay + " for user " + userId
                             + " from settings because user was removed");
                 }
-                mItems.remove(i);
-                removed = true;
-                i--;
+                return true;
             }
-        }
-        return removed;
+            return false;
+        });
     }
 
     /**
      * Reassigns the priority of an overlay maintaining the values of the overlays other settings.
      */
-    void setPriority(@NonNull final String packageName, final int userId, final int priority) {
-        final int moveIdx = select(packageName, userId);
+    void setPriority(@NonNull final OverlayIdentifier overlay, final int userId,
+            final int priority) throws BadKeyException {
+        final int moveIdx = select(overlay, userId);
         if (moveIdx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
 
         final SettingsItem itemToMove = mItems.get(moveIdx);
@@ -224,17 +266,17 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setPriority(@NonNull final String packageName,
-            @NonNull final String newParentPackageName, final int userId) {
-        if (packageName.equals(newParentPackageName)) {
+    boolean setPriority(@NonNull final OverlayIdentifier overlay,
+            @NonNull final OverlayIdentifier newOverlay, final int userId) {
+        if (overlay.equals(newOverlay)) {
             return false;
         }
-        final int moveIdx = select(packageName, userId);
+        final int moveIdx = select(overlay, userId);
         if (moveIdx < 0) {
             return false;
         }
 
-        final int parentIdx = select(newParentPackageName, userId);
+        final int parentIdx = select(newOverlay, userId);
         if (parentIdx < 0) {
             return false;
         }
@@ -248,7 +290,7 @@
         }
 
         mItems.remove(moveIdx);
-        final int newParentIdx = select(newParentPackageName, userId) + 1;
+        final int newParentIdx = select(newOverlay, userId) + 1;
         mItems.add(newParentIdx, itemToMove);
         return moveIdx != newParentIdx;
     }
@@ -256,8 +298,8 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setLowestPriority(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
+    boolean setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
         if (idx <= 0) {
             // If the item doesn't exist or is already the lowest, don't change anything.
             return false;
@@ -272,8 +314,8 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setHighestPriority(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
+    boolean setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
 
         // If the item doesn't exist or is already the highest, don't change anything.
         if (idx < 0 || idx == mItems.size() - 1) {
@@ -297,7 +339,6 @@
                 break;
             }
         }
-
         mItems.add(i + 1, item);
     }
 
@@ -308,7 +349,12 @@
             items = items.filter(item -> item.mUserId == dumpState.getUserId());
         }
         if (dumpState.getPackageName() != null) {
-            items = items.filter(item -> item.mPackageName.equals(dumpState.getPackageName()));
+            items = items.filter(item -> item.mOverlay.getPackageName()
+                    .equals(dumpState.getPackageName()));
+        }
+        if (dumpState.getOverlayName() != null) {
+            items = items.filter(item -> item.mOverlay.getOverlayName()
+                    .equals(dumpState.getOverlayName()));
         }
 
         // display items
@@ -322,10 +368,11 @@
 
     private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw,
             @NonNull final SettingsItem item) {
-        pw.println(item.mPackageName + ":" + item.getUserId() + " {");
+        pw.println(item.mOverlay + ":" + item.getUserId() + " {");
         pw.increaseIndent();
 
-        pw.println("mPackageName...........: " + item.mPackageName);
+        pw.println("mPackageName...........: " + item.mOverlay.getPackageName());
+        pw.println("mOverlayName...........: " + item.mOverlay.getOverlayName());
         pw.println("mUserId................: " + item.getUserId());
         pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
         pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
@@ -335,6 +382,7 @@
         pw.println("mIsMutable.............: " + item.isMutable());
         pw.println("mPriority..............: " + item.mPriority);
         pw.println("mCategory..............: " + item.mCategory);
+        pw.println("mIsFabricated..........: " + item.mIsFabricated);
 
         pw.decreaseIndent();
         pw.println("}");
@@ -344,7 +392,10 @@
             @NonNull final SettingsItem item, @NonNull final String field) {
         switch (field) {
             case "packagename":
-                pw.println(item.mPackageName);
+                pw.println(item.mOverlay.getPackageName());
+                break;
+            case "overlayname":
+                pw.println(item.mOverlay.getOverlayName());
                 break;
             case "userid":
                 pw.println(item.mUserId);
@@ -392,6 +443,7 @@
         private static final String ATTR_BASE_CODE_PATH = "baseCodePath";
         private static final String ATTR_IS_ENABLED = "isEnabled";
         private static final String ATTR_PACKAGE_NAME = "packageName";
+        private static final String ATTR_OVERLAY_NAME = "overlayName";
         private static final String ATTR_STATE = "state";
         private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName";
         private static final String ATTR_TARGET_OVERLAYABLE_NAME = "targetOverlayableName";
@@ -400,30 +452,26 @@
         private static final String ATTR_CATEGORY = "category";
         private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_VERSION = "version";
+        private static final String ATTR_IS_FABRICATED = "fabricated";
 
         @VisibleForTesting
         static final int CURRENT_VERSION = 4;
 
         public static void restore(@NonNull final ArrayList<SettingsItem> table,
                 @NonNull final InputStream is) throws IOException, XmlPullParserException {
+            table.clear();
+            final TypedXmlPullParser parser = Xml.resolvePullParser(is);
+            XmlUtils.beginDocument(parser, TAG_OVERLAYS);
+            final int version = parser.getAttributeInt(null, ATTR_VERSION);
+            if (version != CURRENT_VERSION) {
+                upgrade(version);
+            }
 
-            {
-                table.clear();
-                final TypedXmlPullParser parser = Xml.resolvePullParser(is);
-                XmlUtils.beginDocument(parser, TAG_OVERLAYS);
-                int version = parser.getAttributeInt(null, ATTR_VERSION);
-                if (version != CURRENT_VERSION) {
-                    upgrade(version);
-                }
-                int depth = parser.getDepth();
-
-                while (XmlUtils.nextElementWithin(parser, depth)) {
-                    switch (parser.getName()) {
-                        case TAG_ITEM:
-                            final SettingsItem item = restoreRow(parser, depth + 1);
-                            table.add(item);
-                            break;
-                    }
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_ITEM.equals(parser.getName())) {
+                    final SettingsItem item = restoreRow(parser, depth + 1);
+                    table.add(item);
                 }
             }
         }
@@ -447,7 +495,9 @@
 
         private static SettingsItem restoreRow(@NonNull final TypedXmlPullParser parser,
                 final int depth) throws IOException, XmlPullParserException {
-            final String packageName = XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME);
+            final OverlayIdentifier overlay = new OverlayIdentifier(
+                    XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME),
+                    XmlUtils.readStringAttribute(parser, ATTR_OVERLAY_NAME));
             final int userId = parser.getAttributeInt(null, ATTR_USER_ID);
             final String targetPackageName = XmlUtils.readStringAttribute(parser,
                     ATTR_TARGET_PACKAGE_NAME);
@@ -459,9 +509,11 @@
             final boolean isStatic = parser.getAttributeBoolean(null, ATTR_IS_STATIC, false);
             final int priority = parser.getAttributeInt(null, ATTR_PRIORITY);
             final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
+            final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED,
+                    false);
 
-            return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                    baseCodePath, state, isEnabled, !isStatic, priority, category);
+            return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName,
+                    baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated);
         }
 
         public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -484,7 +536,8 @@
         private static void persistRow(@NonNull final TypedXmlSerializer xml,
                 @NonNull final SettingsItem item) throws IOException {
             xml.startTag(null, TAG_ITEM);
-            XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mPackageName);
+            XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mOverlay.getPackageName());
+            XmlUtils.writeStringAttribute(xml, ATTR_OVERLAY_NAME, item.mOverlay.getOverlayName());
             xml.attributeInt(null, ATTR_USER_ID, item.mUserId);
             XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName);
             XmlUtils.writeStringAttribute(xml, ATTR_TARGET_OVERLAYABLE_NAME,
@@ -495,13 +548,14 @@
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
             xml.attributeInt(null, ATTR_PRIORITY, item.mPriority);
             XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
+            XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated);
             xml.endTag(null, TAG_ITEM);
         }
     }
 
     private static final class SettingsItem {
         private final int mUserId;
-        private final String mPackageName;
+        private final OverlayIdentifier mOverlay;
         private final String mTargetPackageName;
         private final String mTargetOverlayableName;
         private String mBaseCodePath;
@@ -511,13 +565,15 @@
         private boolean mIsMutable;
         private int mPriority;
         private String mCategory;
+        private boolean mIsFabricated;
 
-        SettingsItem(@NonNull final String packageName, final int userId,
+        SettingsItem(@NonNull final OverlayIdentifier overlay, final int userId,
                 @NonNull final String targetPackageName,
                 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
                 final @OverlayInfo.State int state, final boolean isEnabled,
-                final boolean isMutable, final int priority,  @Nullable String category) {
-            mPackageName = packageName;
+                final boolean isMutable, final int priority,  @Nullable String category,
+                final boolean isFabricated) {
+            mOverlay = overlay;
             mUserId = userId;
             mTargetPackageName = targetPackageName;
             mTargetOverlayableName = targetOverlayableName;
@@ -528,6 +584,7 @@
             mCache = null;
             mIsMutable = isMutable;
             mPriority = priority;
+            mIsFabricated = isFabricated;
         }
 
         private String getTargetPackageName() {
@@ -596,8 +653,9 @@
 
         private OverlayInfo getOverlayInfo() {
             if (mCache == null) {
-                mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
-                        mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsMutable);
+                mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(),
+                        mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath,
+                        mState, mUserId, mPriority, mIsMutable, mIsFabricated);
             }
             return mCache;
         }
@@ -620,30 +678,40 @@
         }
     }
 
-    private int select(@NonNull final String packageName, final int userId) {
+    private int select(@NonNull final OverlayIdentifier overlay, final int userId) {
         final int n = mItems.size();
         for (int i = 0; i < n; i++) {
             final SettingsItem item = mItems.get(i);
-            if (item.mUserId == userId && item.mPackageName.equals(packageName)) {
+            if (item.mUserId == userId && item.mOverlay.equals(overlay)) {
                 return i;
             }
         }
         return -1;
     }
 
-    private Stream<SettingsItem> selectWhereUser(final int userId) {
-        return mItems.stream().filter(item -> item.mUserId == userId);
+    private List<SettingsItem> selectWhereUser(final int userId) {
+        final List<SettingsItem> selectedItems = new ArrayList<>();
+        CollectionUtils.addIf(mItems, selectedItems, i -> i.mUserId == userId);
+        return selectedItems;
     }
 
-    private Stream<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+    private List<SettingsItem> selectWhereOverlay(@NonNull final String packageName,
             final int userId) {
-        return selectWhereUser(userId)
-                .filter(item -> item.getTargetPackageName().equals(targetPackageName));
+        final List<SettingsItem> items = selectWhereUser(userId);
+        items.removeIf(i -> !i.mOverlay.getPackageName().equals(packageName));
+        return items;
     }
 
-    static final class BadKeyException extends RuntimeException {
-        BadKeyException(@NonNull final String packageName, final int userId) {
-            super("Bad key mPackageName=" + packageName + " mUserId=" + userId);
+    private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+            final int userId) {
+        final List<SettingsItem> items = selectWhereUser(userId);
+        items.removeIf(i -> !i.getTargetPackageName().equals(targetPackageName));
+        return items;
+    }
+
+    static final class BadKeyException extends Exception {
+        BadKeyException(@NonNull final OverlayIdentifier overlay, final int userId) {
+            super("Bad key '" + overlay + "' for user " + userId );
         }
     }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index bf99bd6..b7b72d1 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -19,20 +19,28 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.om.FabricatedOverlay;
 import android.content.om.IOverlayManager;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.os.Binder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.util.TypedValue;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -72,6 +80,8 @@
                     return runSetPriority();
                 case "lookup":
                     return runLookup();
+                case "fabricate":
+                    return runFabricate();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -89,35 +99,36 @@
         out.println("Overlay manager (overlay) commands:");
         out.println("  help");
         out.println("    Print this help text.");
-        out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
+        out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]");
         out.println("    Print debugging information about the overlay manager.");
-        out.println("    With optional parameter PACKAGE, limit output to the specified");
-        out.println("    package. With optional parameter FIELD, limit output to");
+        out.println("    With optional parameters PACKAGE and NAME, limit output to the specified");
+        out.println("    overlay or target. With optional parameter FIELD, limit output to");
         out.println("    the value of that SettingsItem field. Field names are");
         out.println("    case insensitive and out.println the m prefix can be omitted,");
         out.println("    so the following are equivalent: mState, mstate, State, state.");
-        out.println("  list [--user USER_ID] [PACKAGE]");
+        out.println("  list [--user USER_ID] [PACKAGE[:NAME]]");
         out.println("    Print information about target and overlay packages.");
         out.println("    Overlay packages are printed in priority order. With optional");
-        out.println("    parameter PACKAGE, limit output to the specified package.");
-        out.println("  enable [--user USER_ID] PACKAGE");
-        out.println("    Enable overlay package PACKAGE.");
-        out.println("  disable [--user USER_ID] PACKAGE");
-        out.println("    Disable overlay package PACKAGE.");
-        out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
-        out.println("    Enable overlay package PACKAGE and disable all other overlays for");
-        out.println("    its target package. If the --category option is given, only disables");
-        out.println("    other overlays in the same category.");
+        out.println("    parameters PACKAGE and NAME, limit output to the specified overlay or");
+        out.println("    target.");
+        out.println("  enable [--user USER_ID] PACKAGE[:NAME]");
+        out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME.");
+        out.println("  disable [--user USER_ID] PACKAGE[:NAME]");
+        out.println("    Disable overlay within or owned by PACKAGE with optional unique NAME.");
+        out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE[:NAME]");
+        out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME and");
+        out.println("    disable all other overlays for its target package. If the --category");
+        out.println("    option is given, only disables other overlays in the same category.");
         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
-        out.println("    Change the priority of the overlay PACKAGE to be just higher than");
-        out.println("    the priority of PACKAGE_PARENT If PARENT is the special keyword");
+        out.println("    Change the priority of the overlay to be just higher than");
+        out.println("    the priority of PARENT If PARENT is the special keyword");
         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
         out.println("    If PARENT is the special keyword 'highest', change priority of");
         out.println("    PACKAGE to the highest priority.");
         out.println("  lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
         out.println("    Load a package and print the value of a given resource");
         out.println("    applying the current configuration and enabled overlays.");
-        out.println("    For a more fine-grained alernative, use 'idmap2 lookup'.");
+        out.println("    For a more fine-grained alternative, use 'idmap2 lookup'.");
     }
 
     private int runList() throws RemoteException {
@@ -192,7 +203,7 @@
                 status = "---";
                 break;
         }
-        out.println(String.format("%s %s", status, oi.packageName));
+        out.println(String.format("%s %s", status, oi.getOverlayIdentifier()));
     }
 
     private int runEnableDisable(final boolean enable) throws RemoteException {
@@ -211,8 +222,88 @@
             }
         }
 
-        final String packageName = getNextArgRequired();
-        return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
+        final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
+        mInterface.commit(new OverlayManagerTransaction.Builder()
+                .setEnabled(overlay, enable, userId)
+                .build());
+        return 0;
+    }
+
+    private int runFabricate() throws RemoteException {
+        final PrintWriter err = getErrPrintWriter();
+        if (Binder.getCallingUid() != Process.ROOT_UID) {
+            err.println("Error: must be root to fabricate overlays through the shell");
+            return 1;
+        }
+
+        int userId = UserHandle.USER_SYSTEM;
+        String targetPackage = "";
+        String targetOverlayable = "";
+        String name = "";
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--target":
+                    targetPackage = getNextArgRequired();
+                    break;
+                case "--target-name":
+                    targetOverlayable = getNextArgRequired();
+                    break;
+                case "--name":
+                    name = getNextArgRequired();
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        if (name.isEmpty()) {
+            err.println("Error: Missing required arg '--name'");
+            return 1;
+        }
+
+        if (targetPackage.isEmpty()) {
+            err.println("Error: Missing required arg '--target'");
+            return 1;
+        }
+
+        final String resourceName = getNextArgRequired();
+        final String typeStr = getNextArgRequired();
+        final int type;
+        if (typeStr.startsWith("0x")) {
+            type = Integer.parseUnsignedInt(typeStr.substring(2), 16);
+        } else {
+            type = Integer.parseUnsignedInt(typeStr);
+        }
+        final String dataStr = getNextArgRequired();
+        final int data;
+        if (dataStr.startsWith("0x")) {
+            data = Integer.parseUnsignedInt(dataStr.substring(2), 16);
+        } else {
+            data = Integer.parseUnsignedInt(dataStr);
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            err.println("Error: failed to get package manager");
+            return 1;
+        }
+
+        final String overlayPackageName = "com.android.shell";
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                overlayPackageName, name, targetPackage)
+                .setTargetOverlayable(targetOverlayable)
+                .setResourceValue(resourceName, type, data)
+                .build();
+
+        mInterface.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+        return 0;
     }
 
     private int runEnableExclusive() throws RemoteException {
@@ -234,12 +325,27 @@
                     return 1;
             }
         }
-        final String overlay = getNextArgRequired();
-        if (inCategory) {
-            return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
-        } else {
-            return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
+
+        final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
+        final OverlayInfo overlayInfo = mInterface.getOverlayInfoByIdentifier(overlay, userId);
+        if (overlayInfo == null) {
+            err.println("Error: Unable to get overlay info of: " + overlay);
+            return 1;
         }
+
+        final List<OverlayInfo> overlaysForTarget =
+                mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId);
+        final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
+        for (final OverlayInfo disableOverlay : overlaysForTarget) {
+            if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category))
+                    || !disableOverlay.isMutable) {
+                continue;
+            }
+            builder.setEnabled(disableOverlay.getOverlayIdentifier(), false, userId);
+        }
+        builder.setEnabled(overlayInfo.getOverlayIdentifier(), true, userId);
+        mInterface.commit(builder.build());
+        return 0;
     }
 
     private int runSetPriority() throws RemoteException {
diff --git a/services/core/java/com/android/server/om/PackageManagerHelper.java b/services/core/java/com/android/server/om/PackageManagerHelper.java
index b1a8b4e..750f5c3 100644
--- a/services/core/java/com/android/server/om/PackageManagerHelper.java
+++ b/services/core/java/com/android/server/om/PackageManagerHelper.java
@@ -22,10 +22,15 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
 
 import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -36,6 +41,33 @@
  * @hide
  */
 interface PackageManagerHelper {
+
+    /**
+     * Initializes the helper for the user. This only needs to be invoked one time before
+     * packages of this user are queried.
+     * @param userId the user id to initialize
+     * @return a map of package name to all packages installed in the user
+     */
+    @NonNull
+    ArrayMap<String, AndroidPackage> initializeForUser(final int userId);
+
+    /**
+     * Retrieves the package information if it is installed for the user.
+     */
+    @Nullable
+    AndroidPackage getPackageForUser(@NonNull final String packageName, final int userId);
+
+    /**
+     * Returns whether the package is an instant app.
+     */
+    boolean isInstantApp(@NonNull final String packageName, final int userId);
+
+    /**
+     * @see PackageManager#getPackagesForUid(int)
+     */
+    @Nullable
+    String[] getPackagesForUid(int uid);
+
     /**
      * @return true if the target package has declared an overlayable
      */
@@ -64,11 +96,6 @@
     Map<String, Map<String, String>> getNamedActors();
 
     /**
-     * @see PackageManagerInternal#getOverlayPackages(int)
-     */
-    List<PackageInfo> getOverlayPackages(int userId);
-
-    /**
      * Read from the APK and AndroidManifest of a package to return the overlayable defined for
      * a given name.
      *
@@ -80,19 +107,6 @@
             throws IOException;
 
     /**
-     * @see PackageManager#getPackagesForUid(int)
-     */
-    @Nullable
-    String[] getPackagesForUid(int uid);
-
-    /**
-     * @param userId user to filter package visibility by
-     * @see PackageManager#getPackageInfo(String, int)
-     */
-    @Nullable
-    PackageInfo getPackageInfo(@NonNull String packageName, int userId);
-
-    /**
      * @return true if {@link PackageManagerServiceUtils#compareSignatures} run on both packages
      *     in the system returns {@link PackageManager#SIGNATURE_MATCH}
      */
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 402f646..af0aa76 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -217,7 +217,7 @@
             // trade-off worth doing to save boot time work.
             int result = pm.performDexOptWithStatus(new DexoptOptions(
                     pkg,
-                    PackageManagerService.REASON_BOOT,
+                    PackageManagerService.REASON_POST_BOOT,
                     DexoptOptions.DEXOPT_BOOT_COMPLETE));
             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
                 updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 15e1d52..7bf7042 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -59,6 +59,7 @@
 import com.android.server.utils.Snappable;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.WatchableImpl;
+import com.android.server.utils.Watched;
 import com.android.server.utils.WatchedSparseArray;
 import com.android.server.utils.WatchedSparseBooleanArray;
 import com.android.server.utils.Watcher;
@@ -123,6 +124,7 @@
     private final CookiePersistence mCookiePersistence;
 
     /** State for uninstalled instant apps */
+    @Watched
     @GuardedBy("mService.mLock")
     private final WatchedSparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
 
@@ -132,10 +134,12 @@
      * The value is a set of instant app UIDs.
      * UserID -> TargetAppId -> InstantAppId
      */
+    @Watched
     @GuardedBy("mService.mLock")
     private final WatchedSparseArray<WatchedSparseArray<WatchedSparseBooleanArray>> mInstantGrants;
 
     /** The set of all installed instant apps. UserID -> AppID */
+    @Watched
     @GuardedBy("mService.mLock")
     private final WatchedSparseArray<WatchedSparseBooleanArray> mInstalledInstantAppUids;
 
@@ -189,6 +193,7 @@
         mUninstalledInstantApps.registerObserver(mObserver);
         mInstantGrants.registerObserver(mObserver);
         mInstalledInstantAppUids.registerObserver(mObserver);
+        Watchable.verifyWatchedAttributes(this, mObserver);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index f240d85..e91bb46 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -929,6 +929,7 @@
                 // Flag for bubble to make behaviour match documentLaunchMode=always.
                 intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                 intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true);
             }
 
             intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -1317,6 +1318,10 @@
                     mListeners.finishBroadcast();
                 }
                 super.onPackageAdded(packageName, uid);
+                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+                pmi.registerInstalledLoadingProgressCallback(packageName,
+                        new PackageLoadingProgressCallback(packageName, user),
+                        user.getIdentifier());
             }
 
             @Override
@@ -1538,5 +1543,38 @@
                 checkCallbackCount();
             }
         }
+
+        class PackageLoadingProgressCallback extends
+                PackageManagerInternal.InstalledLoadingProgressCallback {
+            private String mPackageName;
+            private UserHandle mUser;
+
+            PackageLoadingProgressCallback(String packageName, UserHandle user) {
+                super(mCallbackHandler);
+                mPackageName = packageName;
+                mUser = user;
+            }
+
+            @Override
+            public void onLoadingProgressChanged(float progress) {
+                final int n = mListeners.beginBroadcast();
+                try {
+                    for (int i = 0; i < n; i++) {
+                        IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                        BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+                        if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
+                            continue;
+                        }
+                        try {
+                            listener.onPackageLoadingProgressChanged(mUser, mPackageName, progress);
+                        } catch (RemoteException re) {
+                            Slog.d(TAG, "Callback failed ", re);
+                        }
+                    }
+                } finally {
+                    mListeners.finishBroadcast();
+                }
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fc02b34..b9e3e0f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -687,7 +687,8 @@
         boolean generateCompactDex = true;
         switch (compilationReason) {
             case PackageManagerService.REASON_FIRST_BOOT:
-            case PackageManagerService.REASON_BOOT:
+            case PackageManagerService.REASON_BOOT_AFTER_OTA:
+            case PackageManagerService.REASON_POST_BOOT:
             case PackageManagerService.REASON_INSTALL:
                  generateCompactDex = false;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5918652..7bf66c5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -780,17 +780,18 @@
     // Compilation reasons.
     public static final int REASON_UNKNOWN = -1;
     public static final int REASON_FIRST_BOOT = 0;
-    public static final int REASON_BOOT = 1;
-    public static final int REASON_INSTALL = 2;
-    public static final int REASON_INSTALL_FAST = 3;
-    public static final int REASON_INSTALL_BULK = 4;
-    public static final int REASON_INSTALL_BULK_SECONDARY = 5;
-    public static final int REASON_INSTALL_BULK_DOWNGRADED = 6;
-    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7;
-    public static final int REASON_BACKGROUND_DEXOPT = 8;
-    public static final int REASON_AB_OTA = 9;
-    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10;
-    public static final int REASON_SHARED = 11;
+    public static final int REASON_BOOT_AFTER_OTA = 1;
+    public static final int REASON_POST_BOOT = 2;
+    public static final int REASON_INSTALL = 3;
+    public static final int REASON_INSTALL_FAST = 4;
+    public static final int REASON_INSTALL_BULK = 5;
+    public static final int REASON_INSTALL_BULK_SECONDARY = 6;
+    public static final int REASON_INSTALL_BULK_DOWNGRADED = 7;
+    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8;
+    public static final int REASON_BACKGROUND_DEXOPT = 9;
+    public static final int REASON_AB_OTA = 10;
+    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
+    public static final int REASON_SHARED = 12;
 
     public static final int REASON_LAST = REASON_SHARED;
 
@@ -1749,6 +1750,11 @@
         public AndroidPackage getPackage(@NonNull String packageName) {
             return getPackageLocked(packageName);
         }
+
+        @Override
+        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+            return mPmInternal.filterAppAccess(packageName, callingUid, userId);
+        }
     }
 
     /**
@@ -5940,6 +5946,21 @@
         }
     }
 
+    // Link watchables to the class
+    private void registerObserver() {
+        mPackages.registerObserver(mWatcher);
+        mSharedLibraries.registerObserver(mWatcher);
+        mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
+        mInstrumentation.registerObserver(mWatcher);
+        mWebInstantAppsDisabled.registerObserver(mWatcher);
+        mAppsFilter.registerObserver(mWatcher);
+        mInstantAppRegistry.registerObserver(mWatcher);
+        mSettings.registerObserver(mWatcher);
+        // If neither "build" attribute is true then this may be a mockito test, and verification
+        // can fail as a false positive.
+        Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
+    }
+
     /**
      * A extremely minimal constructor designed to start up a PackageManagerService instance for
      * testing.
@@ -6023,15 +6044,7 @@
         sSnapshotCorked = true;
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = mLiveComputer;
-
-        // Link up the watchers
-        mPackages.registerObserver(mWatcher);
-        mSharedLibraries.registerObserver(mWatcher);
-        mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
-        mInstrumentation.registerObserver(mWatcher);
-        mWebInstantAppsDisabled.registerObserver(mWatcher);
-        mAppsFilter.registerObserver(mWatcher);
-        Watchable.verifyWatchedAttributes(this, mWatcher);
+        registerObserver();
 
         mPackages.putAll(testParams.packages);
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6185,15 +6198,6 @@
         mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
         mDomainVerificationManager.setConnection(mDomainVerificationConnection);
 
-        // Link up the watchers
-        mPackages.registerObserver(mWatcher);
-        mSharedLibraries.registerObserver(mWatcher);
-        mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
-        mInstrumentation.registerObserver(mWatcher);
-        mWebInstantAppsDisabled.registerObserver(mWatcher);
-        mAppsFilter.registerObserver(mWatcher);
-        Watchable.verifyWatchedAttributes(this, mWatcher);
-
         // Create the computer as soon as the state objects have been installed.  The
         // cached computer is the same as the live computer until the end of the
         // constructor, at which time the invalidation method updates it.  The cache is
@@ -6202,6 +6206,7 @@
         sSnapshotCorked = true;
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = mLiveComputer;
+        registerObserver();
 
         // CHECKSTYLE:OFF IndentationCheck
         synchronized (mInstallLock) {
@@ -11633,10 +11638,7 @@
         //       first boot, as they do not have profile data.
         boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
 
-        // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
-        boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
-
-        if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
+        if (!causeUpgrade && !causeFirstBoot) {
             return;
         }
 
@@ -11653,7 +11655,7 @@
 
         final long startTime = System.nanoTime();
         final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
-                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
                     false /* bootComplete */);
 
         final int elapsedTimeSeconds =
@@ -16158,8 +16160,7 @@
     @Deprecated
     @Override
     public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
-        mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
-        return true;
+        return mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
     }
 
     @Deprecated
@@ -25946,6 +25947,13 @@
         public String getModuleMetadataPackageName() throws RemoteException {
             return PackageManagerService.this.mModuleInfoProvider.getPackageName();
         }
+
+        @Override
+        public boolean hasSha256SigningCertificate(String packageName, byte[] certificate)
+                throws RemoteException {
+            return PackageManagerService.this.hasSigningCertificate(
+                packageName, certificate, CERT_INPUT_SHA256);
+        }
     }
 
     private AndroidPackage getPackage(String packageName) {
@@ -27026,6 +27034,28 @@
         }
 
         @Override
+        public boolean registerInstalledLoadingProgressCallback(String packageName,
+                PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
+            final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(),
+                    userId);
+            if (ps == null) {
+                return false;
+            }
+            if (!ps.isPackageLoading()) {
+                Slog.w(TAG,
+                        "Failed registering loading progress callback. Package is fully loaded.");
+                return false;
+            }
+            if (mIncrementalManager == null) {
+                Slog.w(TAG,
+                        "Failed registering loading progress callback. Incremental is not enabled");
+                return false;
+            }
+            return mIncrementalManager.registerLoadingProgressCallback(ps.getPathString(),
+                    (IPackageLoadingProgressCallback) callback.getBinder());
+        }
+
+        @Override
         public IncrementalStatesInfo getIncrementalStatesInfo(
                 @NonNull String packageName, int filterCallingUid, int userId) {
             final PackageSetting ps = getPackageSettingForUser(packageName, filterCallingUid,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9cd55a6..636db11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -29,7 +29,8 @@
     // Names for compilation reasons.
     public static final String REASON_STRINGS[] = {
         "first-boot",
-        "boot",
+        "boot-after-ota",
+        "post-boot",
         "install",
         "install-fast",
         "install-bulk",
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 311d662..a8a6bce 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -107,9 +107,6 @@
 import com.android.server.LocalServices;
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
-import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
-import com.android.server.pm.verify.domain.DomainVerificationPersistence;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -117,6 +114,9 @@
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationPersistence;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.utils.Watchable;
@@ -489,7 +489,7 @@
     // App-link priority tracking, per-user
     @NonNull
     @Watched
-    final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
+    private final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
 
     final StringBuilder mReadMessages = new StringBuilder();
 
@@ -554,6 +554,7 @@
         mAppIds.registerObserver(mObserver);
         mOtherAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
+        mNextAppLinkGeneration.registerObserver(mObserver);
         mDefaultBrowserApp.registerObserver(mObserver);
 
         Watchable.verifyWatchedAttributes(this, mObserver);
@@ -604,6 +605,7 @@
         mAppIds.registerObserver(mObserver);
         mOtherAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
+        mNextAppLinkGeneration.registerObserver(mObserver);
         mDefaultBrowserApp.registerObserver(mObserver);
 
         Watchable.verifyWatchedAttributes(this, mObserver);
@@ -651,6 +653,7 @@
         mPastSignatures.addAll(r.mPastSignatures);
         mKeySetRefs.putAll(r.mKeySetRefs);
         mRenamedPackages.snapshot(r.mRenamedPackages);
+        mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
         mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
         // mReadMessages
         mPendingPackages.addAll(r.mPendingPackages);
@@ -2709,7 +2712,6 @@
         writeSigningKeySetLPr(serializer, pkg.keySetData);
         writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
         writeKeySetAliasesLPr(serializer, pkg.keySetData);
-        mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
         writeMimeGroupLPr(serializer, pkg.mimeGroups);
 
         serializer.endTag(null, "package");
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 139654e..3576950 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -587,7 +587,7 @@
     private static final int TRON_COMPILATION_REASON_ERROR = 0;
     private static final int TRON_COMPILATION_REASON_UNKNOWN = 1;
     private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2;
-    private static final int TRON_COMPILATION_REASON_BOOT = 3;
+    private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3;
     private static final int TRON_COMPILATION_REASON_INSTALL = 4;
     private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5;
     private static final int TRON_COMPILATION_REASON_AB_OTA = 6;
@@ -605,6 +605,8 @@
     private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18;
     private static final int
             TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19;
+    private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20;
+    private static final int TRON_COMPILATION_REASON_POST_BOOT = 21;
 
     // The annotation to add as a suffix to the compilation reason when dexopt was
     // performed with dex metadata.
@@ -618,7 +620,8 @@
             case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN;
             case "error" : return TRON_COMPILATION_REASON_ERROR;
             case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
-            case "boot" : return TRON_COMPILATION_REASON_BOOT;
+            case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+            case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
             case "install" : return TRON_COMPILATION_REASON_INSTALL;
             case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
             case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA;
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
new file mode 100644
index 0000000..6cdd4df
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 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.pm.parsing.library;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+/**
+ * Updates a package to remove dependency on android.net.ipsec.ike library.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class AndroidNetIpSecIkeUpdater extends PackageSharedLibraryUpdater {
+
+    private static final String LIBRARY_NAME = "android.net.ipsec.ike";
+
+    @Override
+    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+        removeLibrary(parsedPackage, LIBRARY_NAME);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
index 1405a7d..8a8a302 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
@@ -45,6 +45,9 @@
     static {
         final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
 
+        // Remove android.net.ipsec.ike library, it is added to boot classpath since Android S.
+        packageUpdaters.add(new AndroidNetIpSecIkeUpdater());
+
         // Remove com.google.android.maps library.
         packageUpdaters.add(new ComGoogleAndroidMapsUpdater());
 
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 30c334d..32bee58 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Permission definition.
@@ -345,6 +346,14 @@
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
     }
 
+    public boolean isKnownSigner() {
+        return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
+    }
+
+    public Set<String> getKnownCerts() {
+        return mPermissionInfo.knownCerts;
+    }
+
     public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
         if (!oldPackageName.equals(mPermissionInfo.packageName)) {
             return;
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 aff87111..e486f08 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2590,6 +2590,7 @@
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
+        ArraySet<String> isPrivilegedPermissionAllowlisted = null;
         ArraySet<String> shouldGrantSignaturePermission = null;
         ArraySet<String> shouldGrantInternalPermission = null;
         final List<String> requestedPermissions = pkg.getRequestedPermissions();
@@ -2604,7 +2605,14 @@
             if (permission == null) {
                 continue;
             }
-            if (permission.isSignature() && (shouldGrantSignaturePermission(pkg, permission)
+            if (permission.isPrivileged()
+                    && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
+                if (isPrivilegedPermissionAllowlisted == null) {
+                    isPrivilegedPermissionAllowlisted = new ArraySet<>();
+                }
+                isPrivilegedPermissionAllowlisted.add(permissionName);
+            }
+            if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
                     || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) {
                 if (shouldGrantSignaturePermission == null) {
                     shouldGrantSignaturePermission = new ArraySet<>();
@@ -2830,13 +2838,17 @@
 
                     if ((bp.isNormal() && shouldGrantNormalPermission)
                             || (bp.isSignature()
-                                    && ((shouldGrantSignaturePermission != null
-                                            && shouldGrantSignaturePermission.contains(permName))
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantSignaturePermission,
+                                            permName)
                                             || ((bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))
                             || (bp.isInternal()
-                                    && ((shouldGrantInternalPermission != null
-                                            && shouldGrantInternalPermission.contains(permName))
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantInternalPermission,
+                                            permName)
                                             || ((bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))) {
                         // Grant an install permission.
@@ -3343,7 +3355,92 @@
         return allowed;
     }
 
-    private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg,
+    private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
+            @NonNull PackageSetting packageSetting, @NonNull Permission permission) {
+        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+            return true;
+        }
+        final String packageName = pkg.getPackageName();
+        if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        if (!pkg.isPrivileged()) {
+            return true;
+        }
+        if (!Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        final String permissionName = permission.getName();
+        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+            return true;
+        }
+        // Only enforce the allowlist on boot
+        if (!mSystemReady
+                // Updated system apps do not need to be allowlisted
+                && !packageSetting.getPkgState().isUpdatedSystemApp()) {
+            final ApexManager apexManager = ApexManager.getInstance();
+            final String containingApexPackageName =
+                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
+            final boolean isInUpdatedApex = containingApexPackageName != null
+                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+                    MATCH_ACTIVE_PACKAGE));
+            // Apps that are in updated apexs' do not need to be allowlisted
+            if (!isInUpdatedApex) {
+                // it's only a reportable violation if the permission isn't explicitly
+                // denied
+                if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+                    return false;
+                }
+                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+                        + packageName + " (" + pkg.getPath()
+                        + ") not in privapp-permissions allowlist");
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    synchronized (mLock) {
+                        if (mPrivappPermissionsViolations == null) {
+                            mPrivappPermissionsViolations = new ArraySet<>();
+                        }
+                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+                                + permissionName);
+                    }
+                }
+            }
+        }
+        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+    }
+
+    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
             @NonNull Permission bp) {
         // expect single system package
         String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
@@ -3373,8 +3470,7 @@
     private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
             @NonNull PackageSetting pkgSetting, @NonNull Permission bp) {
         boolean allowed = false;
-        final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged();
-        final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission;
+        final boolean isPrivilegedPermission = bp.isPrivileged();
         final boolean isOemPermission = bp.isOem();
         if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
             final String permissionName = bp.getName();
@@ -3386,19 +3482,18 @@
                 final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
                 if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
                         permissionName)) {
-                    allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg,
-                            true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg,
+                    allowed = (isPrivilegedPermission && disabledPkg.isPrivileged())
+                            || (isOemPermission && canGrantOemPermission(disabledPkg,
                             permissionName));
                 }
             } else {
-                allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp))
+                allowed = (isPrivilegedPermission && pkg.isPrivileged())
                         || (isOemPermission && canGrantOemPermission(pkg, permissionName));
             }
             // In any case, don't grant a privileged permission to privileged vendor apps, if
             // the permission's protectionLevel does not have the extra 'vendorPrivileged'
             // flag.
-            if (allowed && isPrivilegedPermission && !isVendorPrivilegedPermission
-                    && pkg.isVendor()) {
+            if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
                 Slog.w(TAG, "Permission " + permissionName
                         + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
                         + " because it isn't a 'vendorPrivileged' permission.");
@@ -3438,6 +3533,11 @@
             // Any pre-installed system app is allowed to get this permission.
             allowed = true;
         }
+        if (!allowed && bp.isKnownSigner()) {
+            // If the permission is to be granted to a known signer then check if any of this
+            // app's signing certificates are in the trusted certificate digest Set.
+            allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
+        }
         // Deferred to be checked under permission data lock inside restorePermissionState().
         //if (!allowed && bp.isDevelopment()) {
         //    // For development permissions, a development permission
@@ -3536,90 +3636,6 @@
         return mPackageManagerInt.getPackageSetting(sourcePackageName);
     }
 
-    private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg,
-            boolean isUpdatedSystemApp, @NonNull Permission permission) {
-        if (!pkg.isPrivileged()) {
-            return false;
-        }
-        final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals(
-                permission.getPackageName());
-        if (!isPlatformPermission) {
-            return true;
-        }
-        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
-            return true;
-        }
-        final String permissionName = permission.getName();
-        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
-            return true;
-        }
-        // Only enforce the allowlist on boot
-        if (!mSystemReady
-                // Updated system apps do not need to be allowlisted
-                && !isUpdatedSystemApp) {
-            final ApexManager apexManager = ApexManager.getInstance();
-            final String packageName = pkg.getPackageName();
-            final String containingApexPackageName =
-                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
-            final boolean isInUpdatedApex = containingApexPackageName != null
-                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
-                    MATCH_ACTIVE_PACKAGE));
-            // Apps that are in updated apexs' do not need to be allowlisted
-            if (!isInUpdatedApex) {
-                // it's only a reportable violation if the permission isn't explicitly
-                // denied
-                if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
-                    return false;
-                }
-                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
-                        + packageName + " (" + pkg.getPath()
-                        + ") not in privapp-permissions allowlist");
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    synchronized (mLock) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
-                                + permissionName);
-                    }
-                }
-            }
-        }
-        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
-    }
-
-    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
-        }
-        return permissions != null && permissions.contains(permission);
-    }
-
-    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
-        }
-        return permissions != null && permissions.contains(permission);
-    }
-
     private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
         if (!pkg.isOem()) {
             return false;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index c521f82..275dd053 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -18,8 +18,10 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Process;
 
@@ -30,10 +32,17 @@
     @NonNull
     private final Context mContext;
 
+    @NonNull
+    private Callback mCallback;
+
     public DomainVerificationEnforcer(@NonNull Context context) {
         mContext = context;
     }
 
+    public void setCallback(@NonNull Callback callback) {
+        mCallback = callback;
+    }
+
     /**
      * Enforced when mutating any state from shell or internally in the system process.
      */
@@ -67,6 +76,11 @@
                             "Caller " + callingUid
                                     + " is not allowed to query domain verification state");
                 }
+
+                mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+                        Binder.getCallingPid(), callingUid,
+                        "Caller " + callingUid + " does not hold "
+                                + android.Manifest.permission.QUERY_ALL_PACKAGES);
                 break;
         }
     }
@@ -84,28 +98,42 @@
                 isAllowed = true;
                 break;
             default:
-                // TODO(b/159952358): Remove permission check? The component package should
-                //  have been checked when the verifier component was first scanned in PMS.
-                mContext.enforcePermission(
-                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
-                        Binder.getCallingPid(), callingUid,
-                        "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT");
+                final int callingPid = Binder.getCallingPid();
+                boolean isLegacyVerificationAgent = false;
+                if (mContext.checkPermission(
+                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, callingPid,
+                        callingUid) != PackageManager.PERMISSION_GRANTED) {
+                    isLegacyVerificationAgent = mContext.checkPermission(
+                            android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+                            callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+                    if (!isLegacyVerificationAgent) {
+                        throw new SecurityException("Caller " + callingUid + " does not hold "
+                                + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT);
+                    }
+                }
+
+                // If the caller isn't a legacy verifier, it needs the QUERY_ALL permission
+                if (!isLegacyVerificationAgent) {
+                    mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+                            callingPid, callingUid, "Caller " + callingUid + " does not hold "
+                                    + android.Manifest.permission.QUERY_ALL_PACKAGES);
+                }
+
                 isAllowed = proxy.isCallerVerifier(callingUid);
                 break;
         }
 
         if (!isAllowed) {
             throw new SecurityException("Caller " + callingUid
-                    + " is not the approved domain verification agent, isVerifier = "
-                    + proxy.isCallerVerifier(callingUid));
+                    + " is not the approved domain verification agent");
         }
     }
 
     /**
      * Enforced when mutating user selection state inside an exposed API method.
      */
-    public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
-            @UserIdInt int targetUserId) throws SecurityException {
+    public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
+            @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
         if (callingUserId != targetUserId) {
             mContext.enforcePermission(
                     Manifest.permission.INTERACT_ACROSS_USERS,
@@ -117,12 +145,51 @@
                 android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
                 Binder.getCallingPid(), callingUid,
                 "Caller is not allowed to edit user selections");
+
+        if (packageName == null) {
+            return true;
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
     }
 
-    public void callerIsLegacyUserSelector(int callingUid) {
+    public boolean callerIsLegacyUserSelector(int callingUid, @UserIdInt int callingUserId,
+            @NonNull String packageName, @UserIdInt int targetUserId) {
         mContext.enforcePermission(
                 android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
                 Binder.getCallingPid(), callingUid,
                 "Caller is not allowed to edit user state");
+
+        if (callingUserId != targetUserId) {
+            if (mContext.checkPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    Binder.getCallingPid(), callingUid) != PackageManager.PERMISSION_GRANTED) {
+                // Legacy API did not enforce this, so for backwards compatibility, fail silently
+                return false;
+            }
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+    }
+
+    public boolean callerIsLegacyUserQuerent(int callingUid, @UserIdInt int callingUserId,
+            @NonNull String packageName, @UserIdInt int targetUserId) {
+        if (callingUserId != targetUserId) {
+            // The legacy API enforces the _FULL variant, so maintain that here
+            mContext.enforcePermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    Binder.getCallingPid(), callingUid,
+                    "Caller is not allowed to edit other users");
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+    }
+
+    public interface Callback {
+        /**
+         * @return true if access to the given package should be filtered and the method failed as
+         * if the package was not installed
+         */
+        boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 0474d78..50fd6e3 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -174,8 +174,10 @@
      * Set aside a legacy user selection that will be restored to a pending
      * {@link DomainVerificationPkgState} once it's added through
      * {@link #addPackage(PackageSetting)}.
+     *
+     * @return true if state changed successfully
      */
-    void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
+    boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
 
     /**
      * Until the legacy APIs are entirely removed, returns the legacy state from the previously
@@ -184,12 +186,6 @@
     int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
 
     /**
-     * Serialize a legacy setting that wasn't attached yet.
-     * TODO: Does this even matter? Should consider for removal.
-     */
-    void writeLegacySettings(TypedXmlSerializer serializer, String name);
-
-    /**
      * Print the verification state and user selection state of a package.
      *
      * @param packageName        the package whose state to change, or all packages if none is
@@ -235,7 +231,8 @@
             throws IllegalArgumentException, NameNotFoundException;
 
 
-    interface Connection extends Function<String, PackageSetting> {
+    interface Connection extends DomainVerificationEnforcer.Callback,
+            Function<String, PackageSetting> {
 
         /**
          * Notify that a settings change has been made and that eventually
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index e24e5bb..fa03274 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -47,12 +47,12 @@
 import com.android.server.SystemService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.PackageSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
 import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -92,9 +92,9 @@
      * immediately attached once its available.
      * <p>
      * Generally this should be not accessed directly. Prefer calling {@link
-     * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
+     * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)}.
      *
-     * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
+     * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)
      **/
     @GuardedBy("mLock")
     @NonNull
@@ -160,6 +160,7 @@
     @Override
     public void setConnection(@NonNull Connection connection) {
         mConnection = connection;
+        mEnforcer.setCallback(mConnection);
     }
 
     @NonNull
@@ -285,7 +286,7 @@
         mEnforcer.assertApprovedVerifier(callingUid, mProxy);
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
-                    true /* forAutoVerify */);
+                    true /* forAutoVerify */, callingUid, null /* userId */);
             ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
             for (String domain : domains) {
                 Integer previousState = stateMap.get(domain);
@@ -389,8 +390,10 @@
 
     public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
             boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
-        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
-                mConnection.getCallingUserId(), userId);
+        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+        }
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
@@ -455,11 +458,18 @@
     public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
             @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
             throws InvalidDomainSetException, NameNotFoundException {
-        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
-                mConnection.getCallingUserId(), userId);
         synchronized (mLock) {
+            final int callingUid = mConnection.getCallingUid();
+            // Pass null for package name here and do the app visibility enforcement inside
+            // getAndValidateAttachedLocked instead, since this has to fail with the same invalid
+            // ID reason if the target app is invisible
+            if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
+                    null /* packageName */, userId)) {
+                throw new InvalidDomainSetException(domainSetId, null,
+                        InvalidDomainSetException.REASON_ID_INVALID);
+            }
             DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
-                    false /* forAutoVerify */);
+                    false /* forAutoVerify */, callingUid, userId);
             DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
             if (enabled) {
                 userState.addHosts(domains);
@@ -556,8 +566,10 @@
     @Override
     public DomainVerificationUserSelection getDomainVerificationUserSelection(
             @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
-        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
-                mConnection.getCallingUserId(), userId);
+        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+        }
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
@@ -844,23 +856,27 @@
     }
 
     @Override
-    public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
-        mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
+    public boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId,
+            int state) {
+        if (!mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            return false;
+        }
         mLegacySettings.add(packageName, userId, state);
         mConnection.scheduleWriteSettings();
+        return true;
     }
 
     @Override
     public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
+        if (!mEnforcer.callerIsLegacyUserQuerent(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        }
         return mLegacySettings.getUserState(packageName, userId);
     }
 
     @Override
-    public void writeLegacySettings(TypedXmlSerializer serializer, String name) {
-
-    }
-
-    @Override
     public void clearPackage(@NonNull String packageName) {
         synchronized (mLock) {
             mAttachedPkgStates.remove(packageName);
@@ -935,10 +951,14 @@
      * Validates parameters provided by an external caller. Checks that an ID is still live and that
      * any provided domains are valid. Should be called at the beginning of each API that takes in a
      * {@link UUID} domain set ID.
+     *
+     * @param userIdForFilter which user to filter app access to, or null if the caller has already
+     *                        validated package visibility
      */
     @GuardedBy("mLock")
     private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
-            @NonNull Set<String> domains, boolean forAutoVerify)
+            @NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
+            @Nullable Integer userIdForFilter)
             throws InvalidDomainSetException, NameNotFoundException {
         if (domainSetId == null) {
             throw new InvalidDomainSetException(null, null,
@@ -952,6 +972,13 @@
         }
 
         String pkgName = pkgState.getPackageName();
+
+        if (userIdForFilter != null
+                && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) {
+            throw new InvalidDomainSetException(domainSetId, null,
+                    InvalidDomainSetException.REASON_ID_INVALID);
+        }
+
         PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
         if (pkgSetting == null || pkgSetting.getPkg() == null) {
             throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc81961..6a441f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -346,8 +346,21 @@
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
     static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
 
-    /** Amount of time (in milliseconds) a toast window can be shown. */
-    public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds
+    /**
+      * Extra time for additional SystemUI animations.
+      * <p>Since legacy apps can add Toast windows directly instead of using Toast APIs,
+      * {@link DisplayPolicy} ensures that the window manager removes toast windows after
+      * TOAST_WINDOW_TIMEOUT. We increase this timeout by TOAST_WINDOW_ANIM_BUFFER to account for
+      * SystemUI's in/out toast animations, so that the toast text is still shown for a minimum
+      * of 3.5 seconds and the animations are finished before window manager removes the window.
+      */
+    public static final int TOAST_WINDOW_ANIM_BUFFER = 600;
+
+    /**
+      * Amount of time (in milliseconds) a toast window can be shown before it's automatically
+      * removed by window manager.
+      */
+    public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER;
 
     /**
      * Lock protecting internal state.  Must not call out into window
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8e0d632..88fdc4a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,11 +90,11 @@
 import android.view.Display;
 import android.view.KeyEvent;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 57cf986..e57d4ce 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -66,7 +66,7 @@
     private static final String KEY_SERVICE_ENABLED = "service_enabled";
 
     /** Default value in absence of {@link DeviceConfig} override. */
-    private static final boolean DEFAULT_SERVICE_ENABLED = false;
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
 
     static final int ORIENTATION_UNKNOWN =
             FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNKNOWN;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 5e681c6..539b413 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -90,6 +90,7 @@
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -152,6 +153,7 @@
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.role.RoleManagerLocal;
@@ -457,6 +459,8 @@
                         synchronized (mCpuTimePerUidFreqLock) {
                             return pullCpuTimePerUidFreqLocked(atomTag, data);
                         }
+                    case FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER:
+                        return pullCpuCyclesPerThreadGroupCluster(atomTag, data);
                     case FrameworkStatsLog.CPU_ACTIVE_TIME:
                         synchronized (mCpuActiveTimeLock) {
                             return pullCpuActiveTimeLocked(atomTag, data);
@@ -781,6 +785,7 @@
         registerCpuTimePerUid();
         registerCpuCyclesPerUidCluster();
         registerCpuTimePerUidFreq();
+        registerCpuCyclesPerThreadGroupCluster();
         registerCpuActiveTime();
         registerCpuClusterTime();
         registerWifiActivityInfo();
@@ -1510,6 +1515,7 @@
     }
 
     int pullCpuCyclesPerUidClusterLocked(int atomTag, List<StatsEvent> pulledData) {
+        // TODO(b/179485697): Remove power profile dependency.
         PowerProfile powerProfile = new PowerProfile(mContext);
         // Frequency index to frequency mapping.
         long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
@@ -1653,6 +1659,81 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private void registerCpuCyclesPerThreadGroupCluster() {
+        // TODO(b/173227907): Register only when supported.
+        int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {3, 4})
+                .build();
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                metadata,
+                DIRECT_EXECUTOR,
+                mStatsCallbackImpl
+        );
+    }
+
+    int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
+        // TODO(b/179485697): Remove power profile dependency.
+        PowerProfile powerProfile = new PowerProfile(mContext);
+        // Frequency index to frequency mapping.
+        long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
+        if (freqs == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        // Frequency index to cluster mapping.
+        int[] freqClusters = new int[freqs.length];
+        // Number of clusters.
+        int clusters;
+
+        // Initialize frequency mappings.
+        {
+            int cluster = 0;
+            long lastFreq = -1;
+            for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex) {
+                long currFreq = freqs[freqIndex];
+                if (currFreq <= lastFreq) {
+                    cluster++;
+                }
+                freqClusters[freqIndex] = cluster;
+                lastFreq = currFreq;
+            }
+
+            clusters = cluster + 1;
+        }
+
+        SystemServiceCpuThreadTimes times = LocalServices.getService(BatteryStatsInternal.class)
+                .getSystemServiceCpuThreadTimes();
+        if (times == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
+                FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER,
+                times.threadCpuTimesUs, clusters, freqs, freqClusters);
+        addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
+                FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER_BINDER,
+                times.binderThreadCpuTimesUs, clusters, freqs, freqClusters);
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private static void addCpuCyclesPerThreadGroupClusterAtoms(
+            int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs,
+            int clusters, long[] freqs, int[] freqClusters) {
+        long[] aggregatedCycles = new long[clusters];
+        long[] aggregatedTimesUs = new long[clusters];
+        for (int i = 0; i < cpuTimesUs.length; ++i) {
+            aggregatedCycles[freqClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
+            aggregatedTimesUs[freqClusters[i]] += cpuTimesUs[i];
+        }
+        for (int cluster = 0; cluster < clusters; ++cluster) {
+            pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                    atomTag, threadGroup, cluster, aggregatedCycles[cluster],
+                    aggregatedTimesUs[cluster] / 1_000));
+        }
+    }
+
     private void registerCpuActiveTime() {
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
index a54288f..e463ee2 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
@@ -33,9 +33,11 @@
  */
 class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
 
-    private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+    // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
+    private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1);
+    // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
     private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
-            Duration.ofMinutes(1);
+            Duration.ofSeconds(20);
     private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
 
     @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
diff --git a/services/core/java/com/android/server/utils/Watchable.java b/services/core/java/com/android/server/utils/Watchable.java
index f936693..44a7459 100644
--- a/services/core/java/com/android/server/utils/Watchable.java
+++ b/services/core/java/com/android/server/utils/Watchable.java
@@ -19,8 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Build;
+import android.util.Log;
 
-import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 
 /**
@@ -61,40 +61,54 @@
     public void dispatchChange(@Nullable Watchable what);
 
     /**
-     * Return true if the field is tagged with @Watched
-     */
-    private static boolean isWatched(Field f) {
-        for (Annotation a : f.getDeclaredAnnotations()) {
-            if (a.annotationType().equals(Watched.class)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Verify that all @Watched {@link Watchable} attributes are being watched by this
      * class.  This requires reflection and only runs in engineering or user debug
      * builds.
+     * @param base The object that contains watched attributes.
+     * @param observer The {@link Watcher} that should be watching these attributes.
+     * @param logOnly If true then log errors; if false then throw an RuntimeExecption on error.
      */
-    static void verifyWatchedAttributes(Object base, Watcher observer) {
-        if (Build.IS_ENG || Build.IS_USERDEBUG) {
-            for (Field f : base.getClass().getDeclaredFields()) {
+    static void verifyWatchedAttributes(Object base, Watcher observer, boolean logOnly) {
+        if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+            return;
+        }
+        for (Field f : base.getClass().getDeclaredFields()) {
+            if (f.getAnnotation(Watched.class) != null) {
+                final String fn = base.getClass().getName() + "." + f.getName();
                 try {
-                    final boolean flagged = isWatched(f);
+                    f.setAccessible(true);
                     final Object o = f.get(base);
-                    final boolean watchable = o instanceof Watchable;
-                    if (flagged && watchable) {
-                        Watchable attr = (Watchable) f.get(base);
+                    if (o instanceof Watchable) {
+                        Watchable attr = (Watchable) (o);
                         if (attr != null && !attr.isRegisteredObserver(observer)) {
-                            throw new RuntimeException(f.getName() + " missing an observer");
+                            if (logOnly) {
+                                Log.e("Watchable", fn + " missing an observer");
+                            } else {
+                                throw new RuntimeException("Watchable " + fn
+                                                           + " missing an observer");
+                            }
                         }
                     }
                 } catch (IllegalAccessException e) {
                     // The field is protected; ignore it.  Other exceptions that may be thrown by
                     // Field.get() are allowed to roll up.
+                    if (logOnly) {
+                        Log.e("Watchable", fn + " not visible");
+                    } else {
+                        throw new RuntimeException("Watchable " + fn + " not visible");
+                    }
                 }
             }
         }
     }
+
+    /**
+     * Verify that all @Watched {@link Watchable} attributes are being watched by this
+     * class.  This calls verifyWatchedAttributes() with logOnly set to false.
+     * @param base The object that contains watched attributes.
+     * @param observer The {@link Watcher} that should be watching these attributes.
+     */
+    static void verifyWatchedAttributes(Object base, Watcher observer) {
+        verifyWatchedAttributes(base, observer, false);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f97af62..f16a646 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -788,7 +788,7 @@
         final boolean sizeCompatFreeform = Settings.Global.getInt(
                 resolver, DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0;
         final boolean supportsNonResizableMultiWindow = Settings.Global.getInt(
-                resolver, DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) != 0;
+                resolver, DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) != 0;
 
         // Transfer any global setting for forcing RTL layout, into a System Property
         DisplayProperties.debug_force_rtl(forceRtl);
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 737f810..8fcdf2e 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -109,12 +109,13 @@
      */
     void setBaseSettingsFilePath(@Nullable String path) {
         AtomicFile settingsFile;
-        if (path != null) {
-            settingsFile = new AtomicFile(new File(path), WM_DISPLAY_COMMIT_TAG);
+        File file = path != null ? new File(path) : null;
+        if (file != null && file.exists()) {
+            settingsFile = new AtomicFile(file, WM_DISPLAY_COMMIT_TAG);
         } else {
+            Slog.w(TAG, "display settings " + path + " does not exist, using vendor defaults");
             settingsFile = getVendorSettingsFile();
         }
-
         setBaseSettingsStorage(new AtomicFileStorage(settingsFile));
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6e8110e..d81181d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -58,6 +58,8 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -1886,6 +1888,7 @@
         // Fill in some deprecated values.
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
+        rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
 
         // Fill in organized child task info for the task created by organizer.
         if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ceebe95..bd93e04 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,6 +847,8 @@
         mWmService.openSurfaceTransaction();
         try {
             applySurfaceChangesTransaction();
+            // Send any pending task-info changes that were queued-up during a layout deferment
+            mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
             mWmService.mSyncEngine.onSurfacePlacement();
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
@@ -859,8 +861,6 @@
             }
         }
 
-        // Send any pending task-info changes that were queued-up during a layout deferment
-        mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
         checkAppTransitionReady(surfacePlacer);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1f8daf6..8b18679 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -104,9 +104,6 @@
     final boolean mCanAddInternalSystemWindow;
     private final boolean mCanStartTasksFromRecents;
 
-    // If non-system overlays from this process can be hidden by the user or app using
-    // HIDE_NON_SYSTEM_OVERLAY_WINDOWS.
-    final boolean mOverlaysCanBeHidden;
     final boolean mCanCreateSystemApplicationOverlay;
     final boolean mCanHideNonSystemOverlayWindows;
     final boolean mCanAcquireSleepToken;
@@ -136,8 +133,6 @@
                         == PERMISSION_GRANTED;
         mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
                 START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
-        mOverlaysCanBeHidden = !mCanAddInternalSystemWindow
-                && !mService.mAtmInternal.isCallerRecents(mUid);
         mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
                 == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
@@ -253,11 +248,6 @@
     }
 
     @Override
-    public void setTransparentRegion(IWindow window, Region region) {
-        mService.setTransparentRegionWindow(this, window, region);
-    }
-
-    @Override
     public void setInsets(IWindow window, int touchableInsets,
             Rect contentInsets, Rect visibleInsets, Region touchableArea) {
         mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
@@ -682,7 +672,7 @@
 
         boolean changed;
 
-        if (mOverlaysCanBeHidden && !mCanCreateSystemApplicationOverlay) {
+        if (!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay) {
             // We want to track non-system apps adding alert windows so we can post an
             // on-going notification for the user to control their visibility.
             if (visible) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9bbbbe0..9c8a997 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -158,6 +158,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -293,6 +294,9 @@
     private static final String ATTR_MIN_HEIGHT = "min_height";
     private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version";
     private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity";
+    private static final String ATTR_LAST_SNAPSHOT_TASK_SIZE = "last_snapshot_task_size";
+    private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets";
+    private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size";
 
     // Set to false to disable the preview that is shown while a new activity
     // is being started.
@@ -540,6 +544,10 @@
     // NOTE: This value needs to be persisted with each task
     private TaskDescription mTaskDescription;
 
+    // Information about the last snapshot that should be persisted with the task to allow SystemUI
+    // to layout without loading all the task snapshots
+    final PersistedTaskSnapshotData mLastTaskSnapshotData;
+
     // If set to true, the task will report that it is not in the floating
     // state regardless of it's root task affiliation. As the floating state drives
     // production of content insets this can be used to preserve them across
@@ -613,8 +621,6 @@
     SurfaceControl.Transaction mMainWindowSizeChangeTransaction;
     Task mMainWindowSizeChangeTask;
 
-    Rect mPreAnimationBounds = new Rect();
-
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -839,13 +845,13 @@
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
             boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
             String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
-            TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
-            int nextTaskId, int callingUid, String callingPackage,
-            @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture,
-            boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
-            ActivityInfo info, IVoiceInteractionSession _voiceSession,
-            IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer,
-            IBinder _launchCookie, boolean _deferTaskAppear) {
+            TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
+            int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
+            String callingPackage, @Nullable String callingFeatureId, int resizeMode,
+            boolean supportsPictureInPicture, boolean _realActivitySuspended,
+            boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info,
+            IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
+            boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear) {
         super(atmService.mWindowManager);
 
         mAtmService = atmService;
@@ -855,7 +861,12 @@
         mUserId = _userId;
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
-        mTaskDescription = _lastTaskDescription;
+        mTaskDescription = _lastTaskDescription != null
+                ? _lastTaskDescription
+                : new TaskDescription();
+        mLastTaskSnapshotData = _lastSnapshotData != null
+                ? _lastSnapshotData
+                : new PersistedTaskSnapshotData();
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
         setOrientation(SCREEN_ORIENTATION_UNSET);
         mRemoteToken = new RemoteToken(this);
@@ -3899,6 +3910,7 @@
     }
 
     void onSnapshotChanged(TaskSnapshot snapshot) {
+        mLastTaskSnapshotData.set(snapshot);
         mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(
                 mTaskId, snapshot);
     }
@@ -4157,7 +4169,6 @@
     void fillTaskInfo(TaskInfo info, boolean stripExtras) {
         getNumRunningActivities(mReuseActivitiesReport);
         info.userId = isLeafTask() ? mUserId : mCurrentUser;
-        info.stackId = getRootTaskId();
         info.taskId = mTaskId;
         info.displayId = getDisplayId();
         info.isRunning = getTopNonFinishingActivity() != null;
@@ -4707,6 +4718,19 @@
         out.attributeInt(null, ATTR_MIN_HEIGHT, mMinHeight);
         out.attributeInt(null, ATTR_PERSIST_TASK_VERSION, PERSIST_TASK_VERSION);
 
+        if (mLastTaskSnapshotData.taskSize != null) {
+            out.attribute(null, ATTR_LAST_SNAPSHOT_TASK_SIZE,
+                    mLastTaskSnapshotData.taskSize.flattenToString());
+        }
+        if (mLastTaskSnapshotData.contentInsets != null) {
+            out.attribute(null, ATTR_LAST_SNAPSHOT_CONTENT_INSETS,
+                    mLastTaskSnapshotData.contentInsets.flattenToString());
+        }
+        if (mLastTaskSnapshotData.bufferSize != null) {
+            out.attribute(null, ATTR_LAST_SNAPSHOT_BUFFER_SIZE,
+                    mLastTaskSnapshotData.bufferSize.flattenToString());
+        }
+
         if (affinityIntent != null) {
             out.startTag(null, TAG_AFFINITYINTENT);
             affinityIntent.saveToXml(out);
@@ -4774,6 +4798,7 @@
         int taskId = INVALID_TASK_ID;
         final int outerDepth = in.getDepth();
         TaskDescription taskDescription = new TaskDescription();
+        PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
         int taskAffiliation = INVALID_TASK_ID;
         int prevTaskId = INVALID_TASK_ID;
         int nextTaskId = INVALID_TASK_ID;
@@ -4883,6 +4908,15 @@
                 case ATTR_PERSIST_TASK_VERSION:
                     persistTaskVersion = Integer.parseInt(attrValue);
                     break;
+                case ATTR_LAST_SNAPSHOT_TASK_SIZE:
+                    lastSnapshotData.taskSize = Point.unflattenFromString(attrValue);
+                    break;
+                case ATTR_LAST_SNAPSHOT_CONTENT_INSETS:
+                    lastSnapshotData.contentInsets = Rect.unflattenFromString(attrValue);
+                    break;
+                case ATTR_LAST_SNAPSHOT_BUFFER_SIZE:
+                    lastSnapshotData.bufferSize = Point.unflattenFromString(attrValue);
+                    break;
                 default:
                     if (!attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
                         Slog.w(TAG, "Task: Unknown attribute=" + attrName);
@@ -4977,6 +5011,7 @@
                 .setLastTimeMoved(lastTimeOnTop)
                 .setNeverRelinquishIdentity(neverRelinquishIdentity)
                 .setLastTaskDescription(taskDescription)
+                .setLastSnapshotData(lastSnapshotData)
                 .setTaskAffiliation(taskAffiliation)
                 .setPrevAffiliateTaskId(prevTaskId)
                 .setNextAffiliateTaskId(nextTaskId)
@@ -7904,6 +7939,7 @@
         private long mLastTimeMoved;
         private boolean mNeverRelinquishIdentity;
         private TaskDescription mLastTaskDescription;
+        private PersistedTaskSnapshotData mLastSnapshotData;
         private int mTaskAffiliation;
         private int mPrevAffiliateTaskId = INVALID_TASK_ID;
         private int mNextAffiliateTaskId = INVALID_TASK_ID;
@@ -8104,6 +8140,11 @@
             return this;
         }
 
+        private Builder setLastSnapshotData(PersistedTaskSnapshotData lastSnapshotData) {
+            mLastSnapshotData = lastSnapshotData;
+            return this;
+        }
+
         private Builder setOrigActivity(ComponentName origActivity) {
             mOrigActivity = origActivity;
             return this;
@@ -8217,9 +8258,6 @@
             mCallingPackage = mActivityInfo.packageName;
             mResizeMode = mActivityInfo.resizeMode;
             mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();
-            if (mLastTaskDescription == null) {
-                mLastTaskDescription = new TaskDescription();
-            }
 
             final Task task = buildInner();
             task.mHasBeenVisible = mHasBeenVisible;
@@ -8253,9 +8291,9 @@
             return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
                     mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
                     mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
-                    mNeverRelinquishIdentity, mLastTaskDescription, mTaskAffiliation,
-                    mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid, mCallingPackage,
-                    mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
+                    mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
+                    mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
+                    mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
                     mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
                     mActivityInfo, mVoiceSession, mVoiceInteractor, mCreatedByOrganizer,
                     mLaunchCookie, mDeferTaskAppear);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3c7bab3..c54c978 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -977,7 +977,7 @@
         void updateSupportsNonResizableMultiWindow() {
             ContentResolver resolver = mContext.getContentResolver();
             final boolean supportsNonResizableMultiWindow = Settings.Global.getInt(resolver,
-                    DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) != 0;
+                    DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) != 0;
 
             mAtmService.mSupportsNonResizableMultiWindow = supportsNonResizableMultiWindow;
         }
@@ -2150,23 +2150,6 @@
         Slog.i(tag, s, e);
     }
 
-    void setTransparentRegionWindow(Session session, IWindow client, Region region) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                WindowState w = windowForClientLocked(session, client, false);
-                ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE transparentRegionHint=%s: %s",
-                        region, w);
-
-                if ((w != null) && w.mHasSurface) {
-                    w.mWinAnimator.setTransparentRegionHintLocked(region);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
     void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
             Rect visibleInsets, Region touchableRegion) {
         int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fd3d9ba..a94b0aa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3100,7 +3100,7 @@
     }
 
     void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) {
-        if (!mSession.mOverlaysCanBeHidden
+        if (mSession.mCanAddInternalSystemWindow
                 || (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fe70dc1..ece256e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -678,14 +678,6 @@
         }
     }
 
-    void setTransparentRegionHintLocked(final Region region) {
-        if (mSurfaceController == null) {
-            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
-            return;
-        }
-        mSurfaceController.setTransparentRegionHint(region);
-    }
-
     boolean setWallpaperOffset(int dx, int dy, float scale) {
         if (mXOffset == dx && mYOffset == dy && Float.compare(mWallpaperScale, scale) == 0) {
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 82ba3c1..636f0bb 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -194,22 +194,6 @@
         return true;
     }
 
-    void setTransparentRegionHint(final Region region) {
-        if (mSurfaceControl == null) {
-            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
-            return;
-        }
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
-        mService.openSurfaceTransaction();
-        try {
-            getGlobalTransaction().setTransparentRegionHint(mSurfaceControl, region);
-        } finally {
-            mService.closeSurfaceTransaction("setTransparentRegion");
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                    "<<< CLOSE TRANSACTION setTransparentRegion");
-        }
-    }
-
     void setOpaque(boolean isOpaque) {
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 91be056..1c4b034 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -56,10 +56,10 @@
         "com_android_server_vibrator_VibratorController.cpp",
         "com_android_server_VibratorManagerService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
-        "com_android_server_am_CachedAppOptimizer.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
         "onload.cpp",
+        ":lib_cachedAppOptimizer_native",
         ":lib_networkStatsFactory_native",
     ],
 
@@ -193,3 +193,10 @@
         "com_android_server_net_NetworkStatsFactory.cpp",
     ],
 }
+
+filegroup {
+    name: "lib_cachedAppOptimizer_native",
+    srcs: [
+        "com_android_server_am_CachedAppOptimizer.cpp",
+    ],
+}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 31cc295..4551d49 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -278,6 +278,11 @@
     return retVal;
 }
 
+static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env,
+                                                                            jobject clazz) {
+    return env->NewStringUTF(CGROUP_FREEZE_PATH);
+}
+
 static const JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
         {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
@@ -286,7 +291,9 @@
          (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
         {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
         {"getBinderFreezeInfo", "(I)I",
-         (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}};
+         (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
+        {"getFreezerCheckPath", "()Ljava/lang/String;",
+         (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
 {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 404b0cf..07eb7bf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15985,6 +15985,9 @@
     public UserHandle createAndProvisionManagedProfile(
             @NonNull ManagedProfileProvisioningParams provisioningParams,
             @NonNull String callerPackage) {
+        Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+        Objects.requireNonNull(callerPackage, "callerPackage is null");
+
         final ComponentName admin = provisioningParams.getProfileAdminComponentName();
         Objects.requireNonNull(admin, "admin is null");
 
@@ -15992,6 +15995,8 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
+        provisioningParams.logParams(callerPackage);
+
         UserInfo userInfo = null;
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -16291,9 +16296,12 @@
 
     @Override
     public void provisionFullyManagedDevice(
-            FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
-        ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
+            @NonNull FullyManagedDeviceProvisioningParams provisioningParams,
+            @NonNull String callerPackage) {
+        Objects.requireNonNull(provisioningParams, "provisioningParams is null.");
+        Objects.requireNonNull(callerPackage, "callerPackage is null.");
 
+        ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
         Objects.requireNonNull(deviceAdmin, "admin is null.");
         Objects.requireNonNull(provisioningParams.getOwnerName(), "owner name is null.");
 
@@ -16301,6 +16309,8 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
+        provisioningParams.logParams(callerPackage);
+
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO(b/178187130): This check fails silent provisioning, uncomment once silent
@@ -16318,8 +16328,10 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
+            final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    ? UserHandle.USER_SYSTEM : caller.getUserId();
             if (!removeNonRequiredAppsForManagedDevice(
-                    caller.getUserId(),
+                    deviceOwnerUserId,
                     provisioningParams.isLeaveAllSystemAppsEnabled(),
                     deviceAdmin)) {
                 throw new ServiceSpecificException(
@@ -16327,15 +16339,16 @@
                         "PackageManager failed to remove non required apps.");
             }
 
+
             if (!setActiveAdminAndDeviceOwner(
-                    caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) {
+                    deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) {
                 throw new ServiceSpecificException(
                         PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED,
                         "Failed to set device owner.");
             }
 
             disallowAddUser();
-            setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(),
+            setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
                     provisioningParams.canDeviceOwnerGrantSensorsPermissions());
         } catch (Exception e) {
             DevicePolicyEventLogger
@@ -16378,30 +16391,42 @@
     }
 
     private boolean removeNonRequiredAppsForManagedDevice(
-            int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
+            @UserIdInt int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
         Set<String> packagesToDelete = leaveAllSystemAppsEnabled
                 ? Collections.emptySet()
                 : mOverlayPackagesProvider.getNonRequiredApps(
                         admin, userId, ACTION_PROVISION_MANAGED_DEVICE);
+
+        removeNonInstalledPackages(packagesToDelete, userId);
         if (packagesToDelete.isEmpty()) {
+            Slog.i(LOG_TAG, "No packages to delete on user " + userId);
             return true;
         }
+
         NonRequiredPackageDeleteObserver packageDeleteObserver =
                 new NonRequiredPackageDeleteObserver(packagesToDelete.size());
         for (String packageName : packagesToDelete) {
-            if (isPackageInstalledForUser(packageName, userId)) {
-                Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
-                mContext.getPackageManager().deletePackageAsUser(
-                        packageName,
-                        packageDeleteObserver,
-                        PackageManager.DELETE_SYSTEM_APP,
-                        userId);
-            }
+            Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
+            mContext.getPackageManager().deletePackageAsUser(
+                    packageName,
+                    packageDeleteObserver,
+                    PackageManager.DELETE_SYSTEM_APP,
+                    userId);
         }
         Slog.i(LOG_TAG, "Waiting for non required apps to be deleted");
         return packageDeleteObserver.awaitPackagesDeletion();
     }
 
+    private void removeNonInstalledPackages(Set<String> packages, @UserIdInt int userId) {
+        final Set<String> toBeRemoved = new HashSet<>();
+        for (String packageName : packages) {
+            if (!isPackageInstalledForUser(packageName, userId)) {
+                toBeRemoved.add(packageName);
+            }
+        }
+        packages.removeAll(toBeRemoved);
+    }
+
     private void disallowAddUser() {
         if (mInjector.userManagerIsHeadlessSystemUserMode()) {
             Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 98c3b99..97e7582 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -525,8 +525,7 @@
             String filename = "/data/system/heapdump/fdtrack-" + date + ".hprof";
             Debug.dumpHprofData(filename);
         } catch (IOException ex) {
-            Slog.e("System", "Failed to dump fdtrack hprof");
-            ex.printStackTrace();
+            Slog.e("System", "Failed to dump fdtrack hprof", ex);
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d863194..c2e0b77 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.content.pm.PackageUserState
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.content.pm.parsing.component.ParsedActivity
@@ -25,7 +26,6 @@
 import android.os.Build
 import android.os.Process
 import android.util.ArraySet
-import android.util.Singleton
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.pm.PackageSetting
@@ -46,14 +46,11 @@
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.testng.Assert.assertThrows
 import java.io.File
 import java.util.UUID
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
 
-private typealias Enforcer = DomainVerificationEnforcer
-
 @RunWith(Parameterized::class)
 class DomainVerificationEnforcerTest {
 
@@ -64,64 +61,27 @@
         private const val VERIFIER_UID = Process.FIRST_APPLICATION_UID + 1
         private const val NON_VERIFIER_UID = Process.FIRST_APPLICATION_UID + 2
 
-        private const val TEST_PKG = "com.test"
+        private const val VISIBLE_PKG = "com.test.visible"
+        private val VISIBLE_UUID = UUID.fromString("8db01272-270d-4606-a3db-bb35228ff9a2")
+        private const val INVISIBLE_PKG = "com.test.invisible"
+        private val INVISIBLE_UUID = UUID.fromString("16dcb029-d96c-4a19-833a-4c9d72e2ebc3")
 
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
         fun parameters(): Array<Any> {
+            val visiblePkg = mockPkg(VISIBLE_PKG)
+            val visiblePkgSetting = mockPkgSetting(VISIBLE_PKG, VISIBLE_UUID)
+            val invisiblePkg = mockPkg(INVISIBLE_PKG)
+            val invisiblePkgSetting = mockPkgSetting(INVISIBLE_PKG, INVISIBLE_UUID)
+
             val makeEnforcer: (Context) -> DomainVerificationEnforcer = {
-                DomainVerificationEnforcer(it)
-            }
-
-            val mockPkg = mockThrowOnUnmocked<AndroidPackage> {
-                whenever(packageName) { TEST_PKG }
-                whenever(targetSdkVersion) { Build.VERSION_CODES.S }
-                whenever(activities) {
-                    listOf(
-                        ParsedActivity().apply {
-                            addIntent(
-                                ParsedIntentInfo().apply {
-                                    autoVerify = true
-                                    addAction(Intent.ACTION_VIEW)
-                                    addCategory(Intent.CATEGORY_BROWSABLE)
-                                    addCategory(Intent.CATEGORY_DEFAULT)
-                                    addDataScheme("https")
-                                    addDataAuthority("example.com", null)
-                                }
-                            )
+                DomainVerificationEnforcer(it).apply {
+                    setCallback(mockThrowOnUnmocked {
+                        whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+                        whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+                            true
                         }
-                    )
-                }
-            }
-
-            val uuid = UUID.randomUUID()
-
-            // TODO: PackageSetting field encapsulation to move to whenever(name)
-            val mockPkgSetting = spyThrowOnUnmocked(
-                PackageSetting(
-                    TEST_PKG,
-                    TEST_PKG,
-                    File("/test"),
-                    null,
-                    null,
-                    null,
-                    null,
-                    1,
-                    0,
-                    0,
-                    0,
-                    null,
-                    null,
-                    null,
-                    uuid
-                )
-            ) {
-                whenever(getPkg()) { mockPkg }
-                whenever(domainSetId) { uuid }
-                whenever(userState) {
-                    SparseArray<PackageUserState>().apply {
-                        this[0] = PackageUserState()
-                    }
+                    })
                 }
             }
 
@@ -129,142 +89,160 @@
                 {
                     val callingUidInt = AtomicInteger(-1)
                     val callingUserIdInt = AtomicInteger(-1)
-                    Triple(
-                        callingUidInt, callingUserIdInt, DomainVerificationService(
-                            it,
-                            mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
-                            mockThrowOnUnmocked {
-                                whenever(
-                                    isChangeEnabled(
-                                        anyLong(),
-                                        any()
-                                    )
-                                ) { true }
-                            }).apply {
-                                setConnection(mockThrowOnUnmocked {
-                                    whenever(callingUid) { callingUidInt.get() }
-                                    whenever(callingUserId) { callingUserIdInt.get() }
-                                    whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting }
-                                    whenever(getPackageLocked(TEST_PKG)) { mockPkg }
-                                    whenever(schedule(anyInt(), any()))
-                                    whenever(scheduleWriteSettings())
-                                })
+
+                    val connection: DomainVerificationManagerInternal.Connection =
+                        mockThrowOnUnmocked {
+                            whenever(callingUid) { callingUidInt.get() }
+                            whenever(callingUserId) { callingUserIdInt.get() }
+                            whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
+                            whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
+                            whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
+                            whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
+                            whenever(schedule(anyInt(), any()))
+                            whenever(scheduleWriteSettings())
+                            whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+                            whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+                                true
+                            }
                         }
-                    )
+                    val service = DomainVerificationService(
+                        it,
+                        mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
+                        mockThrowOnUnmocked {
+                            whenever(
+                                isChangeEnabled(
+                                    anyLong(),
+                                    any()
+                                )
+                            ) { true }
+                        }).apply {
+                        setConnection(connection)
+                    }
+
+                    Triple(callingUidInt, callingUserIdInt, service)
                 }
 
             fun enforcer(
                 type: Type,
                 name: String,
-                block: DomainVerificationEnforcer.(
-                    callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
-                ) -> Unit
-            ) = Params(
-                type,
-                makeEnforcer,
-                name
-            ) { enforcer, callingUid, callingUserId, userId, proxy ->
-                enforcer.block(callingUid, callingUserId, userId, proxy)
+                block: DomainVerificationEnforcer.(Params.Input<DomainVerificationEnforcer>) -> Any?
+            ) = Params(type, makeEnforcer, name) {
+                it.target.block(it)
             }
 
             fun service(
                 type: Type,
                 name: String,
-                block: DomainVerificationService.(
-                    callingUid: Int, callingUserId: Int, userId: Int
-                ) -> Unit
-            ) = Params(
-                type,
-                makeService,
-                name
-            ) { uidAndUserIdAndService, callingUid, callingUserId, userId, proxy ->
-                val (callingUidInt, callingUserIdInt, service) = uidAndUserIdAndService
-                callingUidInt.set(callingUid)
-                callingUserIdInt.set(callingUserId)
-                service.setProxy(proxy)
-                service.addPackage(mockPkgSetting)
-                service.block(callingUid, callingUserId, userId)
+                block: DomainVerificationService.(Params.Input<Triple<AtomicInteger, AtomicInteger, DomainVerificationService>>) -> Any?
+            ) = Params(type, makeService, name) {
+                val (callingUidInt, callingUserIdInt, service) = it.target
+                callingUidInt.set(it.callingUid)
+                callingUserIdInt.set(it.callingUserId)
+                service.proxy = it.proxy
+                service.addPackage(visiblePkgSetting)
+                service.addPackage(invisiblePkgSetting)
+                service.block(it)
             }
 
             return arrayOf(
-                enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ ->
-                    assertInternal(callingUid)
+                enforcer(Type.INTERNAL, "internal") {
+                    assertInternal(it.callingUid)
                 },
-                enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy ->
-                    assertApprovedQuerent(callingUid, proxy)
+                enforcer(Type.QUERENT, "approvedQuerent") {
+                    assertApprovedQuerent(it.callingUid, it.proxy)
                 },
-                enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy ->
-                    assertApprovedVerifier(callingUid, proxy)
+                enforcer(Type.VERIFIER, "approvedVerifier") {
+                    assertApprovedVerifier(it.callingUid, it.proxy)
                 },
                 enforcer(
                     Type.SELECTOR,
                     "approvedUserSelector"
-                ) { callingUid, callingUserId, userId, _ ->
-                    assertApprovedUserSelector(callingUid, callingUserId, userId)
+                ) {
+                    assertApprovedUserSelector(
+                        it.callingUid, it.callingUserId,
+                        it.targetPackageName, it.userId
+                    )
                 },
-
-                service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ ->
+                service(Type.INTERNAL, "setStatusInternalPackageName") {
                     setDomainVerificationStatusInternal(
-                        TEST_PKG,
+                        it.targetPackageName,
                         DomainVerificationManager.STATE_SUCCESS,
                         ArraySet(setOf("example.com"))
                     )
                 },
-                service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId ->
+                service(Type.INTERNAL, "setUserSelectionInternal") {
                     setDomainVerificationUserSelectionInternal(
-                        userId,
-                        TEST_PKG,
+                        it.userId,
+                        it.targetPackageName,
                         false,
                         ArraySet(setOf("example.com"))
                     )
                 },
-                service(Type.INTERNAL, "verifyPackages") { _, _, _ ->
-                    verifyPackages(listOf(TEST_PKG), true)
+                service(Type.INTERNAL, "verifyPackages") {
+                    verifyPackages(listOf(it.targetPackageName), true)
                 },
-                service(Type.INTERNAL, "clearState") { _, _, _ ->
-                    clearDomainVerificationState(listOf(TEST_PKG))
+                service(Type.INTERNAL, "clearState") {
+                    clearDomainVerificationState(listOf(it.targetPackageName))
                 },
-                service(Type.INTERNAL, "clearUserSelections") { _, _, userId ->
-                    clearUserSelections(listOf(TEST_PKG), userId)
+                service(Type.INTERNAL, "clearUserSelections") {
+                    clearUserSelections(listOf(it.targetPackageName), it.userId)
                 },
-                service(Type.VERIFIER, "getPackageNames") { _, _, _ ->
+                service(Type.VERIFIER, "getPackageNames") {
                     validVerificationPackageNames
                 },
-                service(Type.QUERENT, "getInfo") { _, _, _ ->
-                    getDomainVerificationInfo(TEST_PKG)
+                service(Type.QUERENT, "getInfo") {
+                    getDomainVerificationInfo(it.targetPackageName)
                 },
-                service(Type.VERIFIER, "setStatus") { _, _, _ ->
+                service(Type.VERIFIER, "setStatus") {
                     setDomainVerificationStatus(
-                        uuid,
+                        it.targetDomainSetId,
                         setOf("example.com"),
                         DomainVerificationManager.STATE_SUCCESS
                     )
                 },
-                service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ ->
+                service(Type.VERIFIER, "setStatusInternalUid") {
                     setDomainVerificationStatusInternal(
-                        callingUid,
-                        uuid,
+                        it.callingUid,
+                        it.targetDomainSetId,
                         setOf("example.com"),
                         DomainVerificationManager.STATE_SUCCESS
                     )
                 },
-                service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ ->
-                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
+                service(Type.SELECTOR, "setLinkHandlingAllowed") {
+                    setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true)
                 },
-                service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId ->
-                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId)
+                service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
+                    setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId)
                 },
-                service(Type.SELECTOR, "getUserSelection") { _, _, _ ->
-                    getDomainVerificationUserSelection(TEST_PKG)
+                service(Type.SELECTOR, "getUserSelection") {
+                    getDomainVerificationUserSelection(it.targetPackageName)
                 },
-                service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId ->
-                    getDomainVerificationUserSelection(TEST_PKG, userId)
+                service(Type.SELECTOR_USER, "getUserSelectionUserId") {
+                    getDomainVerificationUserSelection(it.targetPackageName, it.userId)
                 },
-                service(Type.SELECTOR, "setUserSelection") { _, _, _ ->
-                    setDomainVerificationUserSelection(uuid, setOf("example.com"), true)
+                service(Type.SELECTOR, "setUserSelection") {
+                    setDomainVerificationUserSelection(
+                        it.targetDomainSetId,
+                        setOf("example.com"),
+                        true
+                    )
                 },
-                service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId ->
-                    setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId)
+                service(Type.SELECTOR_USER, "setUserSelectionUserId") {
+                    setDomainVerificationUserSelection(
+                        it.targetDomainSetId,
+                        setOf("example.com"),
+                        true,
+                        it.userId
+                    )
+                },
+                service(Type.LEGACY_SELECTOR, "setLegacyUserState") {
+                    setLegacyUserState(
+                        it.targetPackageName, it.userId,
+                        PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER
+                    )
+                },
+                service(Type.LEGACY_QUERENT, "getLegacyUserState") {
+                    getLegacyState(it.targetPackageName, it.userId)
                 },
             )
         }
@@ -273,9 +251,7 @@
             val type: Type,
             val construct: (context: Context) -> T,
             val name: String,
-            private val method: (
-                T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
-            ) -> Unit
+            private val method: (Input<T>) -> Any?
         ) {
             override fun toString() = "${type}_$name"
 
@@ -284,10 +260,79 @@
                 callingUid: Int,
                 callingUserId: Int,
                 userId: Int,
+                targetPackageName: String,
+                targetDomainSetId: UUID,
                 proxy: DomainVerificationProxy
-            ) {
-                @Suppress("UNCHECKED_CAST")
-                method(target as T, callingUid, callingUserId, userId, proxy)
+            ): Any? = method(
+                Input(
+                    @Suppress("UNCHECKED_CAST")
+                    target as T,
+                    callingUid,
+                    callingUserId,
+                    userId,
+                    targetPackageName,
+                    targetDomainSetId,
+                    proxy
+                )
+            )
+
+            data class Input<T>(
+                val target: T,
+                val callingUid: Int,
+                val callingUserId: Int,
+                val userId: Int,
+                val targetPackageName: String,
+                val targetDomainSetId: UUID,
+                val proxy: DomainVerificationProxy
+            )
+        }
+
+        fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> {
+            whenever(this.packageName) { packageName }
+            whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+            whenever(activities) {
+                listOf(
+                    ParsedActivity().apply {
+                        addIntent(
+                            ParsedIntentInfo().apply {
+                                autoVerify = true
+                                addAction(Intent.ACTION_VIEW)
+                                addCategory(Intent.CATEGORY_BROWSABLE)
+                                addCategory(Intent.CATEGORY_DEFAULT)
+                                addDataScheme("https")
+                                addDataAuthority("example.com", null)
+                            }
+                        )
+                    }
+                )
+            }
+        }
+
+        fun mockPkgSetting(packageName: String, domainSetId: UUID) = spyThrowOnUnmocked(
+            PackageSetting(
+                packageName,
+                packageName,
+                File("/test"),
+                null,
+                null,
+                null,
+                null,
+                1,
+                0,
+                0,
+                0,
+                null,
+                null,
+                null,
+                domainSetId
+            )
+        ) {
+            whenever(getPkg()) { mockPkg(packageName) }
+            whenever(this.domainSetId) { domainSetId }
+            whenever(userState) {
+                SparseArray<PackageUserState>().apply {
+                    this[0] = PackageUserState()
+                }
             }
         }
     }
@@ -309,6 +354,8 @@
             Type.VERIFIER -> approvedVerifier()
             Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
             Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
+            Type.LEGACY_QUERENT -> legacyQuerent()
+            Type.LEGACY_SELECTOR -> legacyUserSelector()
         }.run { /*exhaust*/ }
     }
 
@@ -316,24 +363,30 @@
         val context: Context = mockThrowOnUnmocked()
         val target = params.construct(context)
 
-        INTERNAL_UIDS.forEach { runMethod(target, it) }
-        assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        // Internal doesn't care about visibility
+        listOf(true, false).forEach { visible ->
+            INTERNAL_UIDS.forEach { runMethod(target, it, visible) }
+            assertFails { runMethod(target, VERIFIER_UID, visible) }
+            assertFails {
+                runMethod(target, NON_VERIFIER_UID, visible)
+            }
+        }
     }
 
     fun approvedQuerent() {
         val allowUserSelection = AtomicBoolean(false)
+        val allowPreferredApps = AtomicBoolean(false)
+        val allowQueryAll = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (!allowUserSelection.get()) {
-                    throw SecurityException()
-                }
-            }
+            initPermission(
+                allowUserSelection,
+                android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+            )
+            initPermission(
+                allowPreferredApps,
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS
+            )
+            initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
         }
         val target = params.construct(context)
 
@@ -341,27 +394,41 @@
 
         verifyNoMoreInteractions(context)
 
+        assertFails { runMethod(target, VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        // Check that the verifier only needs QUERY_ALL to pass
+        allowQueryAll.set(true)
         runMethod(target, VERIFIER_UID)
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        allowQueryAll.set(false)
+
+        allowPreferredApps.set(true)
+
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
 
         allowUserSelection.set(true)
 
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowQueryAll.set(true)
+
         runMethod(target, NON_VERIFIER_UID)
     }
 
     fun approvedVerifier() {
-        val shouldThrow = AtomicBoolean(false)
+        val allowDomainVerificationAgent = AtomicBoolean(false)
+        val allowIntentVerificationAgent = AtomicBoolean(false)
+        val allowQueryAll = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (shouldThrow.get()) {
-                    throw SecurityException()
-                }
-            }
+            initPermission(
+                allowDomainVerificationAgent,
+                android.Manifest.permission.DOMAIN_VERIFICATION_AGENT
+            )
+            initPermission(
+                allowIntentVerificationAgent,
+                android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT
+            )
+            initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
         }
         val target = params.construct(context)
 
@@ -369,94 +436,241 @@
 
         verifyNoMoreInteractions(context)
 
+        assertFails { runMethod(target, VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowDomainVerificationAgent.set(true)
+
+        assertFails { runMethod(target, VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowQueryAll.set(true)
+
         runMethod(target, VERIFIER_UID)
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
 
-        shouldThrow.set(true)
+        // Check that v1 verifiers are also allowed through
+        allowDomainVerificationAgent.set(false)
+        allowIntentVerificationAgent.set(true)
 
-        assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        runMethod(target, VERIFIER_UID)
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
     }
 
     fun approvedUserSelector(verifyCrossUser: Boolean) {
-        val allowUserSelection = AtomicBoolean(true)
-        val allowInteractAcrossUsers = AtomicBoolean(true)
+        val allowUserSelection = AtomicBoolean(false)
+        val allowInteractAcrossUsers = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (!allowUserSelection.get()) {
-                    throw SecurityException()
-                }
-            }
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.INTERACT_ACROSS_USERS),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (!allowInteractAcrossUsers.get()) {
-                    throw SecurityException()
-                }
-            }
+            initPermission(
+                allowUserSelection,
+                android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+            )
+            initPermission(
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
         }
         val target = params.construct(context)
 
-        fun runEachTestCaseWrapped(
-            callingUserId: Int,
-            targetUserId: Int,
-            block: (testCase: () -> Unit) -> Unit = { it.invoke() }
-        ) {
-            block { runMethod(target, VERIFIER_UID, callingUserId, targetUserId) }
-            block { runMethod(target, NON_VERIFIER_UID, callingUserId, targetUserId) }
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // User selector makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // User selector doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
         }
 
         val callingUserId = 0
         val notCallingUserId = 1
 
-        runEachTestCaseWrapped(callingUserId, callingUserId)
+        runTestCases(callingUserId, callingUserId, throws = true)
         if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId)
+            runTestCases(callingUserId, notCallingUserId, throws = true)
         }
 
-        allowInteractAcrossUsers.set(false)
+        allowUserSelection.set(true)
 
-        runEachTestCaseWrapped(callingUserId, callingUserId)
-
+        runTestCases(callingUserId, callingUserId, throws = false)
         if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
-                assertThrows(SecurityException::class.java, it)
-            }
-        }
-
-        allowUserSelection.set(false)
-
-        runEachTestCaseWrapped(callingUserId, callingUserId) {
-            assertThrows(SecurityException::class.java, it)
-        }
-        if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
-                assertThrows(SecurityException::class.java, it)
-            }
+            runTestCases(callingUserId, notCallingUserId, throws = true)
         }
 
         allowInteractAcrossUsers.set(true)
 
-        runEachTestCaseWrapped(callingUserId, callingUserId) {
-            assertThrows(SecurityException::class.java, it)
-        }
+        runTestCases(callingUserId, callingUserId, throws = false)
         if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
-                assertThrows(SecurityException::class.java, it)
+            runTestCases(callingUserId, notCallingUserId, throws = false)
+        }
+    }
+
+    private fun legacyUserSelector() {
+        val allowInteractAcrossUsers = AtomicBoolean(false)
+        val allowPreferredApps = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            initPermission(
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
+            initPermission(
+                allowPreferredApps,
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS
+            )
+        }
+        val target = params.construct(context)
+
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // Legacy makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
+        }
+
+        val callingUserId = 0
+        val notCallingUserId = 1
+
+        runTestCases(callingUserId, callingUserId, throws = true)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        allowPreferredApps.set(true)
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        allowInteractAcrossUsers.set(true)
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = false)
+    }
+
+    private fun legacyQuerent() {
+        val allowInteractAcrossUsers = AtomicBoolean(false)
+        val allowInteractAcrossUsersFull = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            initPermission(
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
+            initPermission(
+                allowInteractAcrossUsersFull,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+            )
+        }
+        val target = params.construct(context)
+
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // Legacy makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
+        }
+
+        val callingUserId = 0
+        val notCallingUserId = 1
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        // Legacy requires the _FULL permission, so this should continue to fail
+        allowInteractAcrossUsers.set(true)
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        allowInteractAcrossUsersFull.set(true)
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = false)
+    }
+
+    private fun Context.initPermission(boolean: AtomicBoolean, permission: String) {
+        whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) {
+            if (!boolean.get()) {
+                throw SecurityException()
+            }
+        }
+        whenever(checkPermission(eq(permission), anyInt(), anyInt())) {
+            if (boolean.get()) {
+                PackageManager.PERMISSION_GRANTED
+            } else {
+                PackageManager.PERMISSION_DENIED
             }
         }
     }
 
-    private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) {
-        params.runMethod(target, callingUid, callingUserId, userId, proxy)
+    private fun runMethod(
+        target: Any,
+        callingUid: Int,
+        visible: Boolean = true,
+        callingUserId: Int = 0,
+        userId: Int = 0
+    ): Any? {
+        val packageName = if (visible) VISIBLE_PKG else INVISIBLE_PKG
+        val uuid = if (visible) VISIBLE_UUID else INVISIBLE_UUID
+        return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy)
+    }
+
+    private fun assertFails(block: () -> Any?) {
+        try {
+            val value = block()
+            // Some methods return false rather than throwing, so check that as well
+            if ((value as? Boolean) != false) {
+                // Can also return default value if it's a legacy call
+                if ((value as? Int)
+                    != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
+                ) {
+                    throw AssertionError("Expected call to return false, was $value")
+                }
+            }
+        } catch (e: SecurityException) {
+        } catch (e: PackageManager.NameNotFoundException) {
+        } catch (e: DomainVerificationManager.InvalidDomainSetException) {
+            // Any of these 3 exceptions are considered failures, which is expected
+        }
     }
 
     enum class Type {
@@ -473,6 +687,12 @@
         SELECTOR,
 
         // Holding the user setting permission, but targeting cross user
-        SELECTOR_USER
+        SELECTOR_USER,
+
+        // Legacy required no permissions except when cross-user
+        LEGACY_QUERENT,
+
+        // Holding the legacy preferred apps permission
+        LEGACY_SELECTOR
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 5792e02..5629d1c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -267,5 +267,8 @@
             whenever(getPackageLocked(TEST_PKG)) { mockPkg() }
             whenever(schedule(anyInt(), any()))
             whenever(scheduleWriteSettings())
+
+            // This doesn't check for visibility; that's done in the enforcer test
+            whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
         }
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 7c935d5..e7d56a0 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -11,9 +11,16 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+java_defaults {
+    name: "FrameworkMockingServicesTests-jni-defaults",
+    jni_libs: [
+        "libactivitymanagermockingservicestestjni",
+    ],
+}
 
 android_test {
     name: "FrameworksMockingServicesTests",
+    defaults: ["FrameworkMockingServicesTests-jni-defaults"],
 
     srcs: ["src/**/*.java", "src/**/*.kt"],
 
@@ -72,4 +79,4 @@
     libs: [
         "android.test.runner",
     ],
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
new file mode 100644
index 0000000..928065a
--- /dev/null
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -0,0 +1,33 @@
+cc_library_shared {
+    name: "libactivitymanagermockingservicestestjni",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+
+    srcs: [
+        ":lib_cachedAppOptimizer_native",
+        "onload.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/base/libs",
+        "frameworks/native/services",
+        "system/memory/libmeminfo/include",
+    ],
+
+    shared_libs: [
+        "libandroid",
+        "libandroid_runtime",
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libmeminfo",
+        "libnativehelper",
+        "libprocessgroup",
+        "libutils",
+    ],
+}
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
new file mode 100644
index 0000000..147cc47
--- /dev/null
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/*
+ * this is a mini native libaray for cached app optimizer tests to run properly. It
+ * loads all the native methods necessary.
+ */
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+namespace android {
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGE("GetEnv failed!");
+        return result;
+    }
+    ALOG_ASSERT(env, "Could not retrieve the env!");
+    register_android_server_am_CachedAppOptimizer(env);
+    return JNI_VERSION_1_4;
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index d860326..56d30cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -97,6 +97,7 @@
 
     @Before
     public void setUp() {
+        System.loadLibrary("activitymanagermockingservicestestjni");
         mHandlerThread = new HandlerThread("");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0311920..0597443 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,6 +19,8 @@
 import android.os.IPowerManager;
 import android.os.PowerManager.LocationPowerSaveMode;
 
+import com.android.server.location.eventlog.LocationEventLog;
+
 /**
  * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
  * change".
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index c39a77e..6156ba9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,6 +43,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
 
 import org.junit.After;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 8e5b16e..2822d5c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,9 +16,10 @@
 
 package com.android.server.location.injector;
 
+import com.android.server.location.eventlog.LocationEventLog;
+
 public class TestInjector implements Injector {
 
-    private final LocationEventLog mLocationEventLog;
     private final FakeUserInfoHelper mUserInfoHelper;
     private final FakeAlarmHelper mAlarmHelper;
     private final FakeAppOpsHelper mAppOpsHelper;
@@ -32,14 +33,17 @@
     private final LocationUsageLogger mLocationUsageLogger;
 
     public TestInjector() {
-        mLocationEventLog = new LocationEventLog();
+        this(new LocationEventLog());
+    }
+
+    public TestInjector(LocationEventLog eventLog) {
         mUserInfoHelper = new FakeUserInfoHelper();
         mAlarmHelper = new FakeAlarmHelper();
         mAppOpsHelper = new FakeAppOpsHelper();
         mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
         mSettingsHelper = new FakeSettingsHelper();
         mAppForegroundHelper = new FakeAppForegroundHelper();
-        mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(mLocationEventLog);
+        mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
         mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
         mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
         mEmergencyHelper = new FakeEmergencyHelper();
@@ -100,9 +104,4 @@
     public LocationUsageLogger getLocationUsageLogger() {
         return mLocationUsageLogger;
     }
-
-    @Override
-    public LocationEventLog getLocationEventLog() {
-        return mLocationEventLog;
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 54fa89a..66b037d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,6 +83,7 @@
 
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.FakeUserInfoHelper;
 import com.android.server.location.injector.TestInjector;
 
@@ -159,17 +160,19 @@
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
 
-        mInjector = new TestInjector();
+        LocationEventLog eventLog = new LocationEventLog();
+
+        mInjector = new TestInjector(eventLog);
         mInjector.getUserInfoHelper().startUser(OTHER_USER);
 
-        mPassive = new PassiveLocationProviderManager(mContext, mInjector);
+        mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
         mPassive.startManager();
         mPassive.setRealProvider(new PassiveLocationProvider(mContext));
 
         mProvider = new TestProvider(PROPERTIES, IDENTITY);
         mProvider.setProviderAllowed(true);
 
-        mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
+        mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
         mManager.startManager();
         mManager.setRealProvider(mProvider);
     }
diff --git a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
index 50e7a03..58d6dae 100644
--- a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
+++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
@@ -34,7 +34,7 @@
         assertEquals(0, FileUtils.readTextFile(file, 0, null).length());
 
         // The constructor has the side effect of writing to file
-        new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath(), "/dev/null");
+        new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath());
 
         assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0);
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index fdf5095..a946534 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.Context;
@@ -30,15 +28,12 @@
 import android.hardware.power.stats.StateResidencyResult;
 import android.power.PowerStatsInternal;
 import android.util.SparseArray;
-import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 
 import org.junit.Before;
-import org.junit.Test;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -63,44 +58,6 @@
                 mBatteryStatsImpl);
     }
 
-    @Test
-    public void getEnergyConsumptionData() {
-        SparseLongArray expectSubsystems = new SparseLongArray();
-        // Add some energy consumers used by BatteryExternalStatsWorker.
-        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
-                "display");
-        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
-        expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
-
-        // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
-        // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
-        final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
-        mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
-
-        // Inform BESW that PowerStatsInternal is ready to query
-        mBatteryExternalStatsWorker.systemServicesReady();
-
-        MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
-
-        assertEquals(expectSubsystems.size(), energies.size());
-        final int size = expectSubsystems.size();
-
-        for (int i = 0; i < size; i++) {
-            int subsystem = expectSubsystems.keyAt(i);
-            // find the subsystem in the returned MeasuredEnergyArray
-            int subsystemIndex = -1;
-            for (int j = 0; j < size; j++) {
-                if (subsystem == energies.getSubsystem(i)) {
-                    subsystemIndex = i;
-                    break;
-                }
-            }
-            assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
-                    subsystemIndex);
-            assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
-        }
-    }
-
     public class TestInjector extends BatteryExternalStatsWorker.Injector {
         public TestInjector(Context context) {
             super(context);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 67d379a..1efce39 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,18 +16,23 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.power.MeasuredEnergyArray;
+import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
 
-import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -38,134 +43,198 @@
  */
 @SmallTest
 public final class MeasuredEnergySnapshotTest {
-    private static final int NUMBER_SUBSYSTEMS = 3;
-    private static final int SUBSYSTEM_DISPLAY = 0;
-    private static final int SUBSYSTEM_NEVER_USED = 1;
-    private static final int SUBSYSTEM_CATAPULT = 2;
+    private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
+            0, 0, EnergyConsumerType.DISPLAY, "Display");
+    private static final  EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
+            47, 0, EnergyConsumerType.OTHER, "GPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer(
+            1, 1, EnergyConsumerType.OTHER, "HPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer(
+            436, 2, EnergyConsumerType.OTHER, "IPU");
 
-    private MeasuredEnergySnapshot mSnapshot;
+    private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2);
+    private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY);
 
-    // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose.
-    private final int[] mAllSubsystems =
-            {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED};
-    // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT
-    private final int[] mSubsystemIndices = {0, 2, 1};
-    private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0};
-    private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[index];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[index];
-        }
-
-        @Override
-        public int size() {
-            return mAllSubsystems.length;
-        }
+    // Elements in each results are purposefully out of order.
+    private static final  EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null),
+        // No CONSUMER_OTHER_2
     };
-    private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[0];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[0];
-        }
-
-        @Override
-        public int size() {
-            return 1;
-        }
+    private static final  EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null),
+        // No CONSUMER_OTHER_0
+        // No CONSUMER_OTHER_1
+        // No CONSUMER_OTHER_2
+    };
+    private static final  EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] {
+        // No CONSUMER_DISPLAY
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}),
+        // No CONSUMER_OTHER_1
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}),
     };
 
-    @Before
-    public void setUp() {
-        mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray);
+    @Test
+    public void testUpdateAndGetDelta_empty() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertNull(snapshot.updateAndGetDelta(null));
+        assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0]));
     }
 
     @Test
     public void testUpdateAndGetDelta() {
-        SparseLongArray result;
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
 
-        // Increment DISPLAY by 15
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(15, result.get(SUBSYSTEM_DISPLAY));
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
 
-        // Increment DISPLAY by 7
-        // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array.
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5);
-        result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display.
-        assertEquals(1, result.size());
-        assertEquals(7, result.get(SUBSYSTEM_DISPLAY));
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
 
-        // Increment CATAPULT by 64 (in addition to the previous increase of 5)
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT));
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]);
+        assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0.
 
-        // Do nothing
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals("0 results should not appear at all", 0, result.size());
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
 
-        // Increment DISPLAY by 42
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(42, result.get(SUBSYSTEM_DISPLAY));
+        // results2
+        delta = snapshot.updateAndGetDelta(RESULTS_2);
+        assertNotNull(delta);
+        assertEquals(36 - 24, delta.displayEnergyUJ);
+        assertNull(delta.otherUidEnergiesUJ);
+        assertNull(delta.otherTotalEnergyUJ);
 
-        // Increment DISPLAY by 106 and CATAPULT by 13
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(2, result.size());
-        assertEquals(106, result.get(SUBSYSTEM_DISPLAY));
-        assertEquals(13, result.get(SUBSYSTEM_CATAPULT));
+        // results3
+        delta = snapshot.updateAndGetDelta(RESULTS_3);
+        assertNotNull(delta);
+        assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]);
+        assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(3, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2));
+        assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3));
+        assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
+
+        // results4
+        delta = snapshot.updateAndGetDelta(RESULTS_4);
+        assertNotNull(delta);
+        assertEquals(43 - 36, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]);
+        assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data)
+        assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(1, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present
+        assertEquals(1, delta.otherUidEnergiesUJ[2].size());
+        assertEquals(8, delta.otherUidEnergiesUJ[2].get(47));
     }
 
-    private void incrementEnergyOfSubsystem(int subsystem, long energy) {
-        mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy;
+    /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
+    @Test
+    public void testUpdateAndGetDelta_some() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
+
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
+        assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap
+        assertNull(delta.otherUidEnergiesUJ);
     }
 
     @Test
-    public void testUpdateAndGetDelta_null() {
-        assertNull(mSnapshot.updateAndGetDelta(null));
+    public void testGetNumOtherOrdinals() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertEquals(3, snapshot.getNumOtherOrdinals());
     }
 
     @Test
-    public void testHasSubsystem() {
-        // Setup MeasuredEnergySnapshot which reported some of the subsystems.
-        final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT};
-        MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                return subsystems[index];
-            }
+    public void testGetNumOtherOrdinals_none() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+        assertEquals(0, snapshot.getNumOtherOrdinals());
+    }
 
-            @Override
-            public long getEnergy(int index) {
-                return 0; // Irrelevant for this test.
-            }
+    private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
+        final EnergyConsumer ec = new EnergyConsumer();
+        ec.id = id;
+        ec.ordinal = ord;
+        ec.type = type;
+        ec.name = name;
+        return ec;
+    }
 
-            @Override
-            public int size() {
-                return subsystems.length;
-            }
-        };
-        final MeasuredEnergySnapshot snapshot =
-                new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray);
+    private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) {
+        final SparseArray<EnergyConsumer> map = new SparseArray<>();
+        for (EnergyConsumer ec : ecs) {
+            map.put(ec.id, ec);
+        }
+        return map;
+    }
 
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY));
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT));
-        assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED));
+    private static EnergyConsumerResult createEnergyConsumerResult(
+            int id, long energyUWs, int[] uids, long[] uidEnergies) {
+        final EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.id = id;
+        ecr.energyUWs = energyUWs;
+        if (uids != null) {
+            ecr.attribution = new EnergyConsumerAttribution[uids.length];
+            for (int i = 0; i < uids.length; i++) {
+                ecr.attribution[i] = new EnergyConsumerAttribution();
+                ecr.attribution[i].uid = uids[i];
+                ecr.attribution[i].energyUWs = uidEnergies[i];
+            }
+        }
+        return ecr;
+    }
+
+    private void assertNullOrEmpty(SparseLongArray a) {
+        if (a != null) assertEquals("Array should be null or empty", 0, a.size());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index caa46da..d039a9d 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -18,14 +18,23 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.Manifest;
 import android.app.GameManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.HashMap;
+import java.util.function.Supplier;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -37,15 +46,88 @@
     private static final int USER_ID_1 = 1001;
     private static final int USER_ID_2 = 1002;
 
+    // Stolen from ConnectivityServiceTest.MockContext
+    class MockContext extends ContextWrapper {
+        private static final String TAG = "MockContext";
+
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+
+        MockContext(Context base) {
+            super(base);
+        }
+
+        /**
+         * Mock checks for the specified permission, and have them behave as per {@code granted}.
+         *
+         * <p>Passing null reverts to default behavior, which does a real permission check on the
+         * test package.
+         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+         *                {@link PackageManager#PERMISSION_DENIED}.
+         */
+        public void setPermission(String permission, Integer granted) {
+            mMockedPermissions.put(permission, granted);
+        }
+
+        private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+            final Integer granted = mMockedPermissions.get(permission);
+            return granted != null ? granted : ifAbsent.get();
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid) {
+            return checkMockedPermission(
+                    permission, () -> super.checkPermission(permission, pid, uid));
+        }
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return checkMockedPermission(
+                    permission, () -> super.checkCallingOrSelfPermission(permission));
+        }
+
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                super.enforceCallingOrSelfPermission(permission, message);
+                return;
+            }
+
+            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+                throw new SecurityException("[Test] permission denied: " + permission);
+            }
+        }
+    }
+
+    @Mock
+    private MockContext mMockContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = new MockContext(InstrumentationRegistry.getContext());
+    }
+
+    private void mockModifyGameModeGranted() {
+        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+                PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockModifyGameModeDenied() {
+        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+                PackageManager.PERMISSION_DENIED);
+    }
+
     /**
      * By default game mode is not supported.
      */
     @Test
     public void testGameModeDefaultValue() {
-        GameManagerService gameManagerService =
-                new GameManagerService(InstrumentationRegistry.getContext());
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
         gameManagerService.onUserStarting(USER_ID_1);
 
+        mockModifyGameModeGranted();
+
         assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                 gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1));
     }
@@ -55,10 +137,11 @@
      */
     @Test
     public void testDefaultValueForNonexistentUser() {
-        GameManagerService gameManagerService =
-                new GameManagerService(InstrumentationRegistry.getContext());
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
         gameManagerService.onUserStarting(USER_ID_1);
 
+        mockModifyGameModeGranted();
+
         gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2);
         assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                 gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2));
@@ -69,10 +152,11 @@
      */
     @Test
     public void testGameMode() {
-        GameManagerService gameManagerService =
-                new GameManagerService(InstrumentationRegistry.getContext());
+        GameManagerService gameManagerService =  new GameManagerService(mMockContext);
         gameManagerService.onUserStarting(USER_ID_1);
 
+        mockModifyGameModeGranted();
+
         assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                 gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
         gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
@@ -83,4 +167,48 @@
         assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                 gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
     }
+
+    /**
+     * Test permission.MANAGE_GAME_MODE is checked
+     */
+    @Test
+    public void testGetGameModePermissionDenied() {
+        GameManagerService gameManagerService =  new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        // Update the game mode so we can read back something valid.
+        mockModifyGameModeGranted();
+        gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+
+        // Deny permission.MANAGE_GAME_MODE and verify we get back GameManager.GAME_MODE_UNSUPPORTED
+        mockModifyGameModeDenied();
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+    }
+
+    /**
+     * Test permission.MANAGE_GAME_MODE is checked
+     */
+    @Test
+    public void testSetGameModePermissionDenied() {
+        GameManagerService gameManagerService =  new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        // Update the game mode so we can read back something valid.
+        mockModifyGameModeGranted();
+        gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+
+        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+        mockModifyGameModeDenied();
+        gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE,
+                USER_ID_1);
+
+        mockModifyGameModeGranted();
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 8d890b9c..7a4b901 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -103,7 +103,8 @@
     @Test
     public void testNewAuthSession_eligibleSensorsSetToStateUnknown() throws RemoteException {
         setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
-        setupFace(1 /* id */, false /* confirmationAlwaysRequired */);
+        setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
 
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
@@ -118,7 +119,8 @@
 
     @Test
     public void testStartNewAuthSession() throws RemoteException {
-        setupFace(0 /* id */, false /* confirmationAlwaysRequired */);
+        setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
         setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_REAR);
 
         final boolean requireConfirmation = true;
@@ -181,9 +183,6 @@
 
         final long operationId = 123;
         final int userId = 10;
-        final int callingUid = 100;
-        final int callingPid = 1000;
-        final int callingUserId = 10000;
 
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
@@ -220,7 +219,25 @@
         assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
         assertEquals(BiometricSensor.STATE_AUTHENTICATING,
                 session.mPreAuthInfo.eligibleSensors.get(0).getSensorState());
+    }
 
+    @Test
+    public void testCancelAuthentication_whenStateAuthCalled_invokesCancel()
+            throws RemoteException {
+        final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
+
+        setupFace(0 /* id */, false /* confirmationAlwaysRequired */, faceAuthenticator);
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                0 /* operationId */,
+                0 /* userId */);
+
+        session.goToInitialState();
+        assertEquals(STATE_AUTH_CALLED, session.getState());
+        session.onCancelAuthSession(false /* force */);
+
+        verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
     }
 
     private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -282,14 +299,14 @@
                 false /* resetLockoutRequiresHardwareAuthToken */));
     }
 
-    private void setupFace(int id, boolean confirmationAlwaysRequired) throws RemoteException {
-        IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
-        when(faceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-        when(faceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+    private void setupFace(int id, boolean confirmationAlwaysRequired,
+            IBiometricAuthenticator authenticator) throws RemoteException {
+        when(authenticator.isHardwareDetected(any())).thenReturn(true);
+        when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
         mSensors.add(new BiometricSensor(id,
                 TYPE_FACE /* modality */,
                 Authenticators.BIOMETRIC_STRONG /* strength */,
-                faceAuthenticator) {
+                authenticator) {
             @Override
             boolean confirmationAlwaysRequired(int userId) {
                 return confirmationAlwaysRequired;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 2d45726..7dd0734 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -27,6 +27,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
 import android.hardware.biometrics.BiometricConstants;
@@ -159,8 +161,8 @@
 
         // Client 1 cleans up properly
         verify(listener1).onError(eq(TEST_SENSOR_ID), anyInt(),
-                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0));
-        verify(callback1).onClientFinished(eq(client1), eq(true) /* success */);
+                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0));
+        verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
         verify(callback1, never()).onClientStarted(any());
 
         // Client 2 was able to start
@@ -310,6 +312,37 @@
         assertNull(mScheduler.getCurrentClient());
     }
 
+    @Test
+    public void testInterruptPrecedingClients_whenExpected() {
+        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
+                withSettings().extraInterfaces(Interruptable.class));
+
+        final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+        when(interrupter.interruptsPrecedingClients()).thenReturn(true);
+
+        mScheduler.scheduleClientMonitor(interruptableMonitor);
+        mScheduler.scheduleClientMonitor(interrupter);
+        waitForIdle();
+
+        verify((Interruptable) interruptableMonitor).cancel();
+        mScheduler.getInternalCallback().onClientFinished(interruptableMonitor, true /* success */);
+    }
+
+    @Test
+    public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
+        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
+                withSettings().extraInterfaces(Interruptable.class));
+
+        final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+        when(interrupter.interruptsPrecedingClients()).thenReturn(false);
+
+        mScheduler.scheduleClientMonitor(interruptableMonitor);
+        mScheduler.scheduleClientMonitor(interrupter);
+        waitForIdle();
+
+        verify((Interruptable) interruptableMonitor, never()).cancel();
+    }
+
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
     }
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
index 86054e4..27fce3c 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -18,13 +18,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.graphics.FontListParser;
 import android.platform.test.annotations.Presubmit;
+import android.text.FontConfig;
+import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
@@ -38,13 +42,19 @@
 public final class PersistentSystemFontConfigTest {
 
     @Test
-    public void testWriteRead() throws IOException, XmlPullParserException {
+    public void testWriteRead() throws Exception {
         long expectedModifiedDate = 1234567890;
         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
         config.lastModifiedDate = expectedModifiedDate;
         config.updatedFontDirs.add("~~abc");
         config.updatedFontDirs.add("~~def");
 
+        FontConfig.FontFamily fontFamily = parseFontFamily(
+                "<family name='test'>"
+                + "  <font>test.ttf</font>"
+                + "</family>");
+        config.fontFamilies.add(fontFamily);
+
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
             PersistentSystemFontConfig.writeToXml(baos, config);
 
@@ -57,6 +67,7 @@
 
                 assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
                 assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
+                assertThat(another.fontFamilies).containsExactly(fontFamily);
             }
         }
     }
@@ -75,4 +86,11 @@
         }
     }
 
+    private static FontConfig.FontFamily parseFontFamily(String xml) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+        parser.setInput(is, "UTF-8");
+        parser.nextTag();
+        return FontListParser.readFamily(parser, "", null);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index cb83b0f..4bbf96f 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,12 +22,15 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.graphics.FontListParser;
 import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontUpdateRequest;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
 import android.system.Os;
+import android.text.FontConfig;
+import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -37,7 +40,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -49,6 +54,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @Presubmit
 @SmallTest
@@ -157,7 +163,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
         assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
@@ -173,6 +183,14 @@
         assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4);
         // Outdated font dir should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
+        assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
+        assertThat(dir.getFontFamilyMap()).containsKey("foobar");
+        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+        assertThat(foobar.getFontList()).hasSize(2);
+        assertThat(foobar.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("foo.ttf"));
+        assertThat(foobar.getFontList().get(1).getFile())
+                .isEqualTo(dir.getFontFileMap().get("bar.ttf"));
     }
 
     @Test
@@ -184,6 +202,7 @@
                 mConfigFile);
         dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -198,7 +217,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -211,6 +234,7 @@
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -225,7 +249,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -239,6 +267,7 @@
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -253,7 +282,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -274,6 +307,8 @@
         // We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled
         // fonts.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+        // Font family depending on obsoleted font should be removed.
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -285,6 +320,7 @@
                 new File("/dev/null"));
         dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -295,12 +331,19 @@
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
         dirForPreparation.loadFontFileMap();
-        dirForPreparation.update(
-                Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "</family>")));
         try {
             dirForPreparation.update(Arrays.asList(
                     newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
-                    newFontUpdateRequest("bar,2", "Invalid signature")));
+                    newFontUpdateRequest("bar,2", "Invalid signature"),
+                    newAddFontFamilyRequest("<family name='foobar'>"
+                            + "  <font>foo.ttf</font>"
+                            + "  <font>bar.ttf</font>"
+                            + "</family>")));
             fail("Batch update with invalid signature should fail");
         } catch (FontManagerService.SystemFontException e) {
             // Expected
@@ -313,6 +356,11 @@
         // The state should be rolled back as a whole if one of the update requests fail.
         assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+        assertThat(dir.getFontFamilyMap()).containsKey("foobar");
+        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+        assertThat(foobar.getFontList()).hasSize(1);
+        assertThat(foobar.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("foo.ttf"));
     }
 
     @Test
@@ -364,7 +412,7 @@
         dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
         try {
             dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
-            fail("Expect IllegalArgumentException");
+            fail("Expect SystemFontException");
         } catch (FontManagerService.SystemFontException e) {
             assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
         }
@@ -440,7 +488,7 @@
 
         try {
             dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
-            fail("Expect IllegalArgumentException");
+            fail("Expect SystemFontException");
         } catch (FontManagerService.SystemFontException e) {
             assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
         }
@@ -599,6 +647,127 @@
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
     }
 
+    @Test
+    public void addFontFamily() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='test'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>")));
+        assertThat(dir.getFontFileMap()).containsKey("test.ttf");
+        assertThat(dir.getFontFamilyMap()).containsKey("test");
+        FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
+        assertThat(test.getFontList()).hasSize(1);
+        assertThat(test.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+    }
+
+    @Test
+    public void addFontFamily_noName() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Arrays.asList(
+                    newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                    newAddFontFamilyRequest("<family lang='en'>"
+                            + "  <font>test.ttf</font>"
+                            + "</family>")));
+            fail("Expect IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Expect
+        }
+    }
+
+    @Test
+    public void addFontFamily_fontNotAvailable() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
+                    + "  <font>test.ttf</font>"
+                    + "</family>")));
+            fail("Expect IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Expect
+        }
+    }
+
+    @Test
+    public void getSystemFontConfig() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+        // We assume we have monospace.
+        assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                // Updating an existing font family.
+                newAddFontFamilyRequest("<family name='monospace'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>"),
+                // Adding a new font family.
+                newAddFontFamilyRequest("<family name='test'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>")));
+        FontConfig fontConfig = dir.getSystemFontConfig();
+        assertNamedFamilyExists(fontConfig, "monospace");
+        FontConfig.FontFamily monospace = getLastFamily(fontConfig, "monospace");
+        assertThat(monospace.getFontList()).hasSize(1);
+        assertThat(monospace.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+        assertNamedFamilyExists(fontConfig, "test");
+        assertThat(getLastFamily(fontConfig, "test").getFontList())
+                .isEqualTo(monospace.getFontList());
+    }
+
+    @Test
+    public void getSystemFontConfig_preserveFirstFontFamily() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+        assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
+        FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
+        assertThat(firstFontFamily.getName()).isNotEmpty();
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>")));
+        FontConfig fontConfig = dir.getSystemFontConfig();
+        assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
+        assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
+        FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
+        assertThat(updated.getFontList()).hasSize(1);
+        assertThat(updated.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+        assertThat(updated).isNotEqualTo(firstFontFamily);
+    }
+
     private FontUpdateRequest newFontUpdateRequest(String content, String signature)
             throws Exception {
         File file = File.createTempFile("font", "ttf", mCacheDir);
@@ -608,10 +777,36 @@
                 signature.getBytes());
     }
 
+    private static FontUpdateRequest newAddFontFamilyRequest(String xml) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+        parser.setInput(is, "UTF-8");
+        parser.nextTag();
+        FontConfig.FontFamily fontFamily = FontListParser.readFamily(parser, "", null);
+        return new FontUpdateRequest(fontFamily);
+    }
+
     private void writeConfig(PersistentSystemFontConfig.Config config,
             File file) throws IOException {
         try (FileOutputStream fos = new FileOutputStream(file)) {
             PersistentSystemFontConfig.writeToXml(fos, config);
         }
     }
+
+    // Returns the last family with the given name, which will be used for creating Typeface.
+    private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
+        List<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies();
+        for (int i = fontFamilies.size() - 1; i >= 0; i--) {
+            if (familyName.equals(fontFamilies.get(i).getName())) {
+                return fontFamilies.get(i);
+            }
+        }
+        return null;
+    }
+
+    private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
+        assertThat(fontConfig.getFontFamilies().stream()
+                .map(FontConfig.FontFamily::getName)
+                .collect(Collectors.toSet())).contains(familyName);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index da9dcb7..7cb72c4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -28,6 +29,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
@@ -214,4 +216,75 @@
 
         verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_UNKNOWN);
     }
+
+    @Test
+    public void queryDisplayStatus_localDevice_2_0_targetDevice_1_4() throws Exception {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackDevice.mAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(response);
+        mTestLooper.dispatchAll();
+
+        verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    @Test
+    public void queryDisplayStatus_localDevice_2_0_targetDevice_2_0() throws Exception {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+                .buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage reportPowerStatusBroadcast = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(reportPowerStatusBroadcast);
+        mTestLooper.dispatchAll();
+        mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackDevice.mAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+        verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    @Test
+    public void queryDisplayStatus_localDevice_2_0_targetDevice_2_0_unknown() throws Exception {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+                .buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage reportPowerStatusBroadcast = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        mNativeWrapper.onCecMessage(reportPowerStatusBroadcast);
+        mTestLooper.dispatchAll();
+        mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackDevice.mAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(response);
+        mTestLooper.dispatchAll();
+
+        verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index e6b56ca..9f0d982 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -67,6 +67,7 @@
     private int mPlaybackLogicalAddress;
     private boolean mWokenUp;
     private boolean mStandby;
+    private boolean mActiveMediaSessionsPaused;
 
     @Mock
     private IPowerManager mIPowerManagerMock;
@@ -97,6 +98,11 @@
                     }
 
                     @Override
+                    void pauseActiveMediaSessions() {
+                        mActiveMediaSessionsPaused = true;
+                    }
+
+                    @Override
                     boolean isStandbyMessageReceived() {
                         return mStandby;
                     }
@@ -392,6 +398,54 @@
     }
 
     @Test
+    public void handleRoutingChange_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleRoutingChange_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingChange_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingChange_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void handleRoutingInformation_otherDevice_None() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -496,6 +550,52 @@
     }
 
     @Test
+    public void handleRoutingInformation_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleRoutingInformation_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void handleSetStreamPath() {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
@@ -831,6 +931,52 @@
     }
 
     @Test
+    public void handleActiveSource_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleActiveSource_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleActiveSource_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleActiveSource_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void losingActiveSource_standbyNow_verifyStandbyMessageIsSentOnNextStandby() {
         // As described in b/161097846.
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
@@ -1158,6 +1304,54 @@
     }
 
     @Test
+    public void handleSetStreamPath_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleSetStreamPath_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleSetStreamPath_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleSetStreamPath_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void oneTouchPlay_SendStandbyOnSleepToTv() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index 25dbc6b..33ea710 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -45,6 +45,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,8 @@
         MockitoAnnotations.initMocks(this);
         final Context context = InstrumentationRegistry.getTargetContext();
         mUserId = ActivityManager.getCurrentUser();
-        mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+        mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0,
+                Process.SHELL_UID);
         when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 3a292de..38125c7 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -21,7 +21,9 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageInfo
 import android.os.Process
+import android.util.ArrayMap
 import com.android.server.om.OverlayActorEnforcer.ActorState
+import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
@@ -29,6 +31,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
+import org.mockito.Mockito
 import org.mockito.Mockito.spy
 import java.io.IOException
 
@@ -125,11 +128,11 @@
                 ActorState.TARGET_NOT_FOUND withCases {
                     failure("nullPkgInfo") { targetPkgInfo = null }
                     allowed("debuggable") {
-                        targetPkgInfo = pkgInfo(TARGET_PKG).apply {
-                            applicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE
+                        targetPkgInfo = androidPackage(TARGET_PKG).apply {
+                            whenever(this.isDebuggable).thenReturn(true)
                         }
                     }
-                    skip { targetPkgInfo = pkgInfo(TARGET_PKG) }
+                    skip { targetPkgInfo = androidPackage(TARGET_PKG) }
                 },
                 ActorState.NO_PACKAGES_FOR_UID withCases {
                     failure("empty") { callingUid = EMPTY_UID }
@@ -236,22 +239,20 @@
                                 mapOf(VALID_ACTOR_NAME to VALID_ACTOR_PKG))
                     }
                 },
-                ActorState.MISSING_APP_INFO withCases {
+                ActorState.ACTOR_NOT_FOUND withCases {
                     failure("nullActorPkgInfo") { actorPkgInfo = null }
                     failure("nullActorAppInfo") {
-                        actorPkgInfo = PackageInfo().apply { applicationInfo = null }
+                        actorPkgInfo = null
                     }
-                    skip { actorPkgInfo = pkgInfo(VALID_ACTOR_PKG) }
+                    skip { actorPkgInfo = androidPackage(VALID_ACTOR_PKG) }
                 },
                 ActorState.ACTOR_NOT_PREINSTALLED withCases {
                     failure("notSystem") {
-                        actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
-                            applicationInfo.flags = 0
-                        }
+                        actorPkgInfo = androidPackage(VALID_ACTOR_PKG)
                     }
                     skip {
-                        actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
-                            applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM
+                        actorPkgInfo = androidPackage(VALID_ACTOR_PKG).apply {
+                            whenever(this.isSystem).thenReturn(true)
                         }
                     }
                 },
@@ -272,22 +273,22 @@
         ) {
             fun toOverlayInfo() = OverlayInfo(
                     OVERLAY_PKG,
+                    "",
                     targetPackageName,
                     targetOverlayableName,
                     null,
                     "/path",
                     OverlayInfo.STATE_UNKNOWN, 0,
-                    0, false)
+                    0, false, false)
         }
 
         private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
                 TestCase(this).apply(block)
 
-        private fun pkgInfo(pkgName: String): PackageInfo = mockThrowOnUnmocked {
-            this.packageName = pkgName
-            this.applicationInfo = ApplicationInfo().apply {
-                this.packageName = pkgName
-            }
+        private fun androidPackage(pkgName: String): AndroidPackage = mockThrowOnUnmocked {
+            whenever(this.packageName).thenReturn(pkgName)
+            whenever(this.isDebuggable).thenReturn(false)
+            whenever(this.isSystem).thenReturn(false)
         }
 
         private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String {
@@ -363,8 +364,8 @@
         var namedActorsMap: Map<String, Map<String, String>> = emptyMap(),
         var hasPermission: Boolean = false,
         var targetOverlayableInfo: OverlayableInfo? = null,
-        var targetPkgInfo: PackageInfo? = null,
-        var actorPkgInfo: PackageInfo? = null,
+        var targetPkgInfo: AndroidPackage? = null,
+        var actorPkgInfo: AndroidPackage? = null,
         vararg val packageNames: String = arrayOf("com.test.actor.one")
     ) : PackageManagerHelper {
 
@@ -375,6 +376,14 @@
 
         override fun getNamedActors() = namedActorsMap
 
+        override fun isInstantApp(packageName: String, userId: Int): Boolean {
+            throw UnsupportedOperationException()
+        }
+
+        override fun initializeForUser(userId: Int): ArrayMap<String, AndroidPackage> {
+            throw UnsupportedOperationException()
+        }
+
         @Throws(IOException::class)
         override fun getOverlayableForTarget(
             packageName: String,
@@ -394,9 +403,6 @@
             else -> null
         }
 
-        override fun getPackageInfo(packageName: String, userId: Int) =
-                listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
-
         @Throws(IOException::class) // Mockito requires this checked exception to be declared
         override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
             return targetOverlayableInfo?.takeIf {
@@ -411,11 +417,10 @@
             }
         }
 
-        override fun getConfigSignaturePackage(): String {
-            throw UnsupportedOperationException()
-        }
+        override fun getPackageForUser(packageName: String, userId: Int) =
+            listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
 
-        override fun getOverlayPackages(userId: Int): MutableList<PackageInfo> {
+        override fun getConfigSignaturePackage(): String {
             throw UnsupportedOperationException()
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 5468fba..55cd772 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -21,7 +21,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.util.ArraySet;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -29,77 +31,66 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
-import java.util.List;
 import java.util.function.BiConsumer;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceImplTestsBase {
 
     private static final String OVERLAY = "com.test.overlay";
+    private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
     private static final String TARGET = "com.test.target";
     private static final int USER = 0;
 
     private static final String OVERLAY2 = OVERLAY + "2";
+    private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
 
     @Test
     public void testUpdateOverlaysForUser() {
         final OverlayManagerServiceImpl impl = getImpl();
+        final String otherTarget = "some.other.target";
         addPackage(target(TARGET), USER);
-        addPackage(target("some.other.target"), USER);
+        addPackage(target(otherTarget), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
         // do nothing, expect no change
-        final List<String> a = impl.updateOverlaysForUser(USER);
-        assertEquals(1, a.size());
-        assertTrue(a.contains(TARGET));
+        final ArraySet<PackageAndUser> a = impl.updateOverlaysForUser(USER);
+        assertEquals(3, a.size());
+        assertTrue(a.containsAll(Arrays.asList(
+                new PackageAndUser(TARGET, USER),
+                new PackageAndUser(otherTarget, USER),
+                new PackageAndUser(OVERLAY, USER))));
 
-        // upgrade overlay, keep target
-        addPackage(overlay(OVERLAY, TARGET), USER);
-
-        final List<String> b = impl.updateOverlaysForUser(USER);
-        assertEquals(1, b.size());
-        assertTrue(b.contains(TARGET));
-
-        // do nothing, expect no change
-        final List<String> c = impl.updateOverlaysForUser(USER);
-        assertEquals(1, c.size());
-        assertTrue(c.contains(TARGET));
-
-        // upgrade overlay, switch to new target
-        addPackage(overlay(OVERLAY, "some.other.target"), USER);
-        final List<String> d = impl.updateOverlaysForUser(USER);
-        assertEquals(2, d.size());
-        assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
-
-        // do nothing, expect no change
-        final List<String> f = impl.updateOverlaysForUser(USER);
-        assertEquals(1, f.size());
-        assertTrue(f.contains("some.other.target"));
+        final ArraySet<PackageAndUser> b = impl.updateOverlaysForUser(USER);
+        assertEquals(3, b.size());
+        assertTrue(b.containsAll(Arrays.asList(
+                new PackageAndUser(TARGET, USER),
+                new PackageAndUser(otherTarget, USER),
+                new PackageAndUser(OVERLAY, USER))));
     }
 
     @Test
     public void testImmutableEnabledChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
 
         configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertFalse(o1.isEnabled());
         assertFalse(o1.isMutable);
 
         configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o2);
         assertTrue(o2.isEnabled());
         assertFalse(o2.isMutable);
 
         configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertFalse(o3.isEnabled());
         assertFalse(o3.isMutable);
@@ -108,26 +99,26 @@
     @Test
     public void testMutableEnabledChangeHasNoEffect() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
 
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertFalse(o1.isEnabled());
         assertTrue(o1.isMutable);
 
         configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o2);
         assertFalse(o2.isEnabled());
         assertTrue(o2.isMutable);
 
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertFalse(o3.isEnabled());
         assertTrue(o3.isMutable);
@@ -136,13 +127,13 @@
     @Test
     public void testMutableEnabledToImmutableEnabled() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
 
         final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
             configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
             impl.updateOverlaysForUser(USER);
-            final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
+            final OverlayInfo o = impl.getOverlayInfo(IDENTIFIER, USER);
             assertNotNull(o);
             assertEquals(enabled, o.isEnabled());
             assertEquals(mutable, o.isMutable);
@@ -180,38 +171,38 @@
     @Test
     public void testMutablePriorityChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertEquals(0, o1.priority);
         assertFalse(o1.isEnabled());
 
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o2);
         assertEquals(1, o2.priority);
         assertFalse(o2.isEnabled());
 
         // Overlay priority changing between reboots should not affect enable state of mutable
         // overlays.
-        impl.setEnabled(OVERLAY, true, USER);
+        impl.setEnabled(IDENTIFIER, true, USER);
 
         // Reorder the overlays
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
         configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertEquals(1, o3.priority);
         assertTrue(o3.isEnabled());
 
-        final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o4);
         assertEquals(0, o4.priority);
         assertFalse(o4.isEnabled());
@@ -220,19 +211,19 @@
     @Test
     public void testImmutablePriorityChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
         configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertEquals(0, o1.priority);
         assertTrue(o1.isEnabled());
 
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o2);
         assertEquals(1, o2.priority);
         assertTrue(o2.isEnabled());
@@ -242,12 +233,12 @@
         configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertEquals(1, o3.priority);
         assertTrue(o3.isEnabled());
 
-        final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o4);
         assertEquals(0, o4.priority);
         assertTrue(o4.isEnabled());
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index 33dbcc0..45f82a3 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.util.Pair;
 
@@ -39,19 +40,23 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase {
 
     private static final String OVERLAY = "com.test.overlay";
+    private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
     private static final String TARGET = "com.test.target";
     private static final int USER = 0;
 
     private static final String OVERLAY2 = OVERLAY + "2";
     private static final String TARGET2 = TARGET + "2";
+    private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
     private static final int USER2 = USER + 1;
 
     private static final String OVERLAY3 = OVERLAY + "3";
+    private static final OverlayIdentifier IDENTIFIER3 = new OverlayIdentifier(OVERLAY3);
     private static final int USER3 = USER2 + 1;
 
     private static final String CONFIG_SIGNATURE_REFERENCE_PKG = "com.test.ref";
@@ -60,10 +65,10 @@
 
     @Test
     public void testGetOverlayInfo() throws Exception {
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
-        final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(oi);
         assertEquals(oi.packageName, OVERLAY);
         assertEquals(oi.targetPackageName, TARGET);
@@ -72,19 +77,19 @@
 
     @Test
     public void testGetOverlayInfosForTarget() throws Exception {
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
-        installNewPackage(overlay(OVERLAY3, TARGET), USER2);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(overlay(OVERLAY3, TARGET), USER2);
 
         final OverlayManagerServiceImpl impl = getImpl();
         final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
         assertEquals(ois.size(), 2);
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
 
         final List<OverlayInfo> ois2 = impl.getOverlayInfosForTarget(TARGET, USER2);
         assertEquals(ois2.size(), 1);
-        assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER2)));
+        assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER2)));
 
         final List<OverlayInfo> ois3 = impl.getOverlayInfosForTarget(TARGET, USER3);
         assertNotNull(ois3);
@@ -97,10 +102,10 @@
 
     @Test
     public void testGetOverlayInfosForUser() throws Exception {
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
-        installNewPackage(overlay(OVERLAY3, TARGET2), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(overlay(OVERLAY3, TARGET2), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
         final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -109,13 +114,13 @@
         final List<OverlayInfo> ois = everything.get(TARGET);
         assertNotNull(ois);
         assertEquals(ois.size(), 2);
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
 
         final List<OverlayInfo> ois2 = everything.get(TARGET2);
         assertNotNull(ois2);
         assertEquals(ois2.size(), 1);
-        assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER)));
+        assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER)));
 
         final Map<String, List<OverlayInfo>> everything2 = impl.getOverlaysForUser(USER2);
         assertNotNull(everything2);
@@ -124,26 +129,26 @@
 
     @Test
     public void testPriority() throws Exception {
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
-        installNewPackage(overlay(OVERLAY3, TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(overlay(OVERLAY3, TARGET), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER3, USER);
 
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
-        assertEquals(impl.setLowestPriority(OVERLAY3, USER),
+        assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
                 Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
 
-        assertEquals(impl.setHighestPriority(OVERLAY3, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+        assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
+                Set.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
-        assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER),
+        assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
                 Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
     }
@@ -151,61 +156,63 @@
     @Test
     public void testOverlayInfoStateTransitions() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        assertNull(impl.getOverlayInfo(OVERLAY, USER));
+        assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
 
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
         final FakeDeviceState.PackageBuilder target = target(TARGET);
-        installNewPackage(target, USER);
-        assertState(STATE_DISABLED, OVERLAY, USER);
+        installPackage(target, USER);
+        assertState(STATE_DISABLED, IDENTIFIER, USER);
 
-        assertEquals(impl.setEnabled(OVERLAY, true, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
-        assertState(STATE_ENABLED, OVERLAY, USER);
+        assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
+                Set.of(new PackageAndUser(TARGET, USER)));
+        assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         // target upgrades do not change the state of the overlay
         upgradePackage(target, USER);
-        assertState(STATE_ENABLED, OVERLAY, USER);
+        assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         uninstallPackage(TARGET, USER);
-        assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+        assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
-        installNewPackage(target, USER);
-        assertState(STATE_ENABLED, OVERLAY, USER);
+        installPackage(target, USER);
+        assertState(STATE_ENABLED, IDENTIFIER, USER);
     }
 
     @Test
     public void testOnOverlayPackageUpgraded() throws Exception {
         final FakeDeviceState.PackageBuilder target = target(TARGET);
         final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
-        installNewPackage(target, USER);
-        installNewPackage(overlay, USER);
+        installPackage(target, USER);
+        installPackage(overlay, USER);
         upgradePackage(overlay, USER);
 
         // upgrade to a version where the overlay has changed its target
         final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
-        final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair =
-                upgradePackage(overlay2, USER);
-        assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER)));
-        assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER)));
+        final Pair<Set<PackageAndUser>, Set<PackageAndUser>> pair = upgradePackage(overlay2, USER);
+        assertEquals(pair.first, Set.of(new PackageAndUser(TARGET, USER)));
+        assertEquals(
+                Set.of(new PackageAndUser(TARGET, USER),
+                        new PackageAndUser("some.other.target", USER)),
+                pair.second);
     }
 
     @Test
     public void testSetEnabledAtVariousConditions() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         assertThrows(OverlayManagerServiceImpl.OperationFailedException.class,
-                () -> impl.setEnabled(OVERLAY, true, USER));
+                () -> impl.setEnabled(IDENTIFIER, true, USER));
 
         // request succeeded, and there was a change that needs to be
         // propagated to the rest of the system
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        assertEquals(impl.setEnabled(OVERLAY, true, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
+                Set.of(new PackageAndUser(TARGET, USER)));
 
         // request succeeded, but nothing changed
-        assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent());
+        assertTrue(impl.setEnabled(IDENTIFIER, true, USER).isEmpty());
     }
 
     @Test
@@ -214,8 +221,8 @@
         reinitializeImpl();
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -232,8 +239,8 @@
         reinitializeImpl();
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -247,8 +254,8 @@
     @Test
     public void testConfigSignaturePolicyNoConfig() throws Exception {
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -261,8 +268,8 @@
 
     @Test
     public void testConfigSignaturePolicyNoRefPkg() throws Exception {
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -279,8 +286,8 @@
         reinitializeImpl();
 
         addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 2c477c8..16e0329 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -24,11 +24,12 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayInfo.State;
 import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -37,17 +38,19 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
+import java.util.Set;
 
 /** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
 class OverlayManagerServiceImplTestsBase {
@@ -94,12 +97,11 @@
         mConfigSignaturePackageName = packageName;
     }
 
-    void assertState(@State int expected, final String overlayPackageName, int userId) {
-        final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
+    void assertState(@State int expected, final OverlayIdentifier overlay, int userId) {
+        final OverlayInfo info = mImpl.getOverlayInfo(overlay, userId);
         if (info == null) {
-            throw new IllegalStateException("package not installed");
+            throw new IllegalStateException("overlay '" + overlay + "' not installed");
         }
-
         final String msg = String.format("expected %s but was %s:",
                 OverlayInfo.stateToString(expected), OverlayInfo.stateToString(info.state));
         assertEquals(msg, expected, info.state);
@@ -152,17 +154,13 @@
      *
      * @throws IllegalStateException if the package is currently installed
      */
-    void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId)
+    Set<PackageAndUser> installPackage(FakeDeviceState.PackageBuilder pkg, int userId)
             throws OperationFailedException {
         if (mState.select(pkg.packageName, userId) != null) {
             throw new IllegalStateException("package " + pkg.packageName + " already installed");
         }
         mState.add(pkg, userId);
-        if (pkg.targetPackage == null) {
-            mImpl.onTargetPackageAdded(pkg.packageName, userId);
-        } else {
-            mImpl.onOverlayPackageAdded(pkg.packageName, userId);
-        }
+        return CollectionUtils.emptyIfNull(mImpl.onPackageAdded(pkg.packageName, userId));
     }
 
     /**
@@ -178,26 +176,21 @@
      *
      * @throws IllegalStateException if the package is not currently installed
      */
-    Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage(
+    Pair<Set<PackageAndUser>, Set<PackageAndUser>> upgradePackage(
             FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException {
         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
         if (replacedPackage == null) {
             throw new IllegalStateException("package " + pkg.packageName + " not installed");
         }
-        Optional<PackageAndUser> opt1 = Optional.empty();
-        if (replacedPackage.targetPackageName != null) {
-            opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
-        }
+
+        final Set<PackageAndUser> updatedPackages1 =
+                CollectionUtils.emptyIfNull(mImpl.onPackageReplacing(pkg.packageName, userId));
 
         mState.add(pkg, userId);
-        Optional<PackageAndUser> opt2;
-        if (pkg.targetPackage == null) {
-            opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId);
-        } else {
-            opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
-        }
+        final Set<PackageAndUser> updatedPackages2 =
+                CollectionUtils.emptyIfNull(mImpl.onPackageReplaced(pkg.packageName, userId));
 
-        return Pair.create(opt1, opt2);
+        return Pair.create(updatedPackages1, updatedPackages2);
     }
 
     /**
@@ -208,17 +201,13 @@
      *
      * @throws IllegalStateException if the package is not currently installed
      */
-    void uninstallPackage(String packageName, int userId) throws OperationFailedException {
+    Set<PackageAndUser> uninstallPackage(String packageName, int userId) {
         final FakeDeviceState.Package pkg = mState.select(packageName, userId);
         if (pkg == null) {
             throw new IllegalStateException("package " + packageName+ " not installed");
         }
         mState.remove(pkg.packageName);
-        if (pkg.targetPackageName == null) {
-            mImpl.onTargetPackageRemoved(pkg.packageName, userId);
-        } else {
-            mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
-        }
+        return CollectionUtils.emptyIfNull(mImpl.onPackageRemoved(packageName, userId));
     }
 
     /** Represents the state of packages installed on a fake device. */
@@ -247,11 +236,6 @@
             }
         }
 
-        List<Package> select(int userId) {
-            return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
-                    .collect(Collectors.toList());
-        }
-
         Package select(String packageName, int userId) {
             final Package pkg = mPackages.get(packageName);
             return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
@@ -335,6 +319,21 @@
                 this.apkPath = apkPath;
                 this.certificate = certificate;
             }
+
+            @Nullable
+            private AndroidPackage getPackageForUser(int user) {
+                if (!installedUserIds.contains(user)) {
+                    return null;
+                }
+                final AndroidPackage pkg = Mockito.mock(AndroidPackage.class);
+                when(pkg.getPackageName()).thenReturn(packageName);
+                when(pkg.getBaseApkPath()).thenReturn(apkPath);
+                when(pkg.getLongVersionCode()).thenReturn((long) versionCode);
+                when(pkg.getOverlayTarget()).thenReturn(targetPackageName);
+                when(pkg.getOverlayTargetName()).thenReturn(targetOverlayableName);
+                when(pkg.getOverlayCategory()).thenReturn("Fake-category-" + targetPackageName);
+                return pkg;
+            }
         }
     }
 
@@ -345,21 +344,29 @@
             mState = state;
         }
 
+        @NonNull
         @Override
-        public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
-            final FakeDeviceState.Package pkg = mState.select(packageName, userId);
-            if (pkg == null) {
-                return null;
-            }
-            final ApplicationInfo ai = new ApplicationInfo();
-            ai.sourceDir = pkg.apkPath;
-            PackageInfo pi = new PackageInfo();
-            pi.applicationInfo = ai;
-            pi.packageName = pkg.packageName;
-            pi.overlayTarget = pkg.targetPackageName;
-            pi.targetOverlayableName = pkg.targetOverlayableName;
-            pi.overlayCategory = "Fake-category-" + pkg.targetPackageName;
-            return pi;
+        public ArrayMap<String, AndroidPackage> initializeForUser(int userId) {
+            final ArrayMap<String, AndroidPackage> packages = new ArrayMap<>();
+            mState.mPackages.forEach((key, value) -> {
+                final AndroidPackage pkg = value.getPackageForUser(userId);
+                if (pkg != null) {
+                    packages.put(key, pkg);
+                }
+            });
+            return packages;
+        }
+
+        @Nullable
+        @Override
+        public AndroidPackage getPackageForUser(@NonNull String packageName, int userId) {
+            final FakeDeviceState.Package pkgState = mState.select(packageName, userId);
+            return pkgState == null ? null : pkgState.getPackageForUser(userId);
+        }
+
+        @Override
+        public boolean isInstantApp(@NonNull String packageName, int userId) {
+            return false;
         }
 
         @Override
@@ -371,14 +378,6 @@
         }
 
         @Override
-        public List<PackageInfo> getOverlayPackages(int userId) {
-            return mState.select(userId).stream()
-                    .filter(p -> p.targetPackageName != null)
-                    .map(p -> getPackageInfo(p.packageName, userId))
-                    .collect(Collectors.toList());
-        }
-
-        @Override
         public @NonNull String getConfigSignaturePackage() {
             return mConfigSignaturePackageName;
         }
@@ -421,6 +420,9 @@
     static class FakeIdmapDaemon extends IdmapDaemon {
         private final FakeDeviceState mState;
         private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
+        private final ArrayMap<String, FabricatedOverlayInfo> mFabricatedOverlays =
+                new ArrayMap<>();
+        private int mFabricatedAssetSeq = 0;
 
         FakeIdmapDaemon(FakeDeviceState state) {
             this.mState = state;
@@ -433,10 +435,10 @@
         }
 
         @Override
-        String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-                int userId) {
+        String createIdmap(String targetPath, String overlayPath, String overlayName,
+                int policies, boolean enforce, int userId) {
             mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
-                    getCrc(overlayPath), targetPath, policies, enforce));
+                    getCrc(overlayPath), targetPath, overlayName, policies, enforce));
             return overlayPath;
         }
 
@@ -446,8 +448,8 @@
         }
 
         @Override
-        boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-                int userId) {
+        boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies,
+                boolean enforce, int userId) {
             final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
             if (idmap == null) {
                 return false;
@@ -461,6 +463,29 @@
             return mIdmapFiles.containsKey(overlayPath);
         }
 
+        @Override
+        FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+            final String path = Integer.toString(mFabricatedAssetSeq++);
+            final FabricatedOverlayInfo info = new FabricatedOverlayInfo();
+            info.path = path;
+            info.overlayName = overlay.overlayName;
+            info.packageName = overlay.packageName;
+            info.targetPackageName = overlay.targetPackageName;
+            info.targetOverlayable = overlay.targetOverlayable;
+            mFabricatedOverlays.put(path, info);
+            return info;
+        }
+
+        @Override
+        boolean deleteFabricatedOverlay(@NonNull String path) {
+            return mFabricatedOverlays.remove(path) != null;
+        }
+
+        @Override
+        List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+            return new ArrayList<>(mFabricatedOverlays.values());
+        }
+
         IdmapHeader getIdmap(String overlayPath) {
             return mIdmapFiles.get(overlayPath);
         }
@@ -469,14 +494,16 @@
             private final int targetCrc;
             private final int overlayCrc;
             final String targetPath;
+            final String overlayName;
             final int policies;
             final boolean enforceOverlayable;
 
-            private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
-                    boolean enforceOverlayable) {
+            private IdmapHeader(int targetCrc, int overlayCrc, String targetPath,
+                    String overlayName, int policies, boolean enforceOverlayable) {
                 this.targetCrc = targetCrc;
                 this.overlayCrc = overlayCrc;
                 this.targetPath = targetPath;
+                this.overlayName = overlayName;
                 this.policies = policies;
                 this.enforceOverlayable = enforceOverlayable;
             }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index e3e7768..0a26f27 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -19,17 +19,20 @@
 import static android.content.om.OverlayInfo.STATE_DISABLED;
 import static android.content.om.OverlayInfo.STATE_ENABLED;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.text.TextUtils;
 import android.util.TypedXmlPullParser;
 import android.util.Xml;
 
+import androidx.annotation.NonNull;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -43,67 +46,32 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.IntStream;
-import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerSettingsTests {
     private OverlayManagerSettings mSettings;
+    private static int USER_0 = 0;
+    private static int USER_1 = 1;
 
-    private static final OverlayInfo OVERLAY_A0 = new OverlayInfo(
-            "com.test.overlay_a",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_a-1/base.apk",
-            STATE_DISABLED,
-            0,
-            0,
-            true);
+    private static OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a",
+            null /* overlayName */);
+    private static OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b",
+            null /* overlayName */);
+    private static OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c",
+            null /* overlayName */);
 
-    private static final OverlayInfo OVERLAY_B0 = new OverlayInfo(
-            "com.test.overlay_b",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_b-1/base.apk",
-            STATE_DISABLED,
-            0,
-            0,
-            true);
+    private static final OverlayInfo OVERLAY_A_USER0 = createInfo(OVERLAY_A, USER_0);
+    private static final OverlayInfo OVERLAY_B_USER0 = createInfo(OVERLAY_B, USER_0);
+    private static final OverlayInfo OVERLAY_C_USER0 = createInfo(OVERLAY_C, USER_0);
 
-    private static final OverlayInfo OVERLAY_C0 = new OverlayInfo(
-            "com.test.overlay_c",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_c-1/base.apk",
-            STATE_DISABLED,
-            0,
-            0,
-            true);
+    private static final OverlayInfo OVERLAY_A_USER1 = createInfo(OVERLAY_A, USER_1);
+    private static final OverlayInfo OVERLAY_B_USER1 = createInfo(OVERLAY_B, USER_1);
 
-    private static final OverlayInfo OVERLAY_A1 = new OverlayInfo(
-            "com.test.overlay_a",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_a-1/base.apk",
-            STATE_DISABLED,
-            1,
-            0,
-            true);
-
-    private static final OverlayInfo OVERLAY_B1 = new OverlayInfo(
-            "com.test.overlay_b",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_b-1/base.apk",
-            STATE_DISABLED,
-            1,
-            0,
-            true);
+    private static final String TARGET_PACKAGE = "com.test.target";
 
     @Before
     public void setUp() throws Exception {
@@ -114,124 +82,112 @@
 
     @Test
     public void testSettingsInitiallyEmpty() throws Exception {
-        final int userId = 0;
-        Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(userId);
+        final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(0 /* userId */);
         assertEquals(0, map.size());
     }
 
     @Test
     public void testBasicSetAndGet() throws Exception {
-        assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+        assertDoesNotContain(mSettings, OVERLAY_A_USER0);
 
-        insert(OVERLAY_A0);
-        assertContains(mSettings, OVERLAY_A0);
-        OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
-        assertEquals(OVERLAY_A0, oi);
+        insertSetting(OVERLAY_A_USER0);
+        assertContains(mSettings, OVERLAY_A_USER0);
+        final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A, USER_0);
+        assertEquals(OVERLAY_A_USER0, oi);
 
-        assertTrue(mSettings.remove(OVERLAY_A0.packageName, OVERLAY_A0.userId));
-        assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+        assertTrue(mSettings.remove(OVERLAY_A, USER_0));
+        assertDoesNotContain(mSettings, OVERLAY_A, USER_0);
     }
 
     @Test
     public void testGetUsers() throws Exception {
-        int[] users = mSettings.getUsers();
-        assertEquals(0, users.length);
+        assertArrayEquals(new int[]{}, mSettings.getUsers());
 
-        insert(OVERLAY_A0);
-        users = mSettings.getUsers();
-        assertEquals(1, users.length);
-        assertContains(users, OVERLAY_A0.userId);
+        insertSetting(OVERLAY_A_USER0);
+        assertArrayEquals(new int[]{USER_0}, mSettings.getUsers());
 
-        insert(OVERLAY_A1);
-        insert(OVERLAY_B1);
-        users = mSettings.getUsers();
-        assertEquals(2, users.length);
-        assertContains(users, OVERLAY_A0.userId);
-        assertContains(users, OVERLAY_A1.userId);
+        insertSetting(OVERLAY_A_USER1);
+        insertSetting(OVERLAY_B_USER1);
+        assertArrayEquals(new int[]{USER_0, USER_1}, mSettings.getUsers());
     }
 
     @Test
     public void testGetOverlaysForUser() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_A1);
-        insert(OVERLAY_B1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_A_USER1);
+        insertSetting(OVERLAY_B_USER0);
 
-        Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(OVERLAY_A0.userId);
-        assertEquals(1, map.keySet().size());
-        assertTrue(map.keySet().contains(OVERLAY_A0.targetPackageName));
+        final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(USER_0);
+        assertEquals(Set.of(TARGET_PACKAGE), map.keySet());
 
-        List<OverlayInfo> list = map.get(OVERLAY_A0.targetPackageName);
-        assertEquals(2, list.size());
-        assertTrue(list.contains(OVERLAY_A0));
-        assertTrue(list.contains(OVERLAY_B0));
+        // Two overlays in user 0 target the same package
+        final List<OverlayInfo> list = map.get(TARGET_PACKAGE);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0), list);
 
-        // getOverlaysForUser should never return null
-        map = mSettings.getOverlaysForUser(-1);
-        assertNotNull(map);
-        assertEquals(0, map.size());
+        // No users installed for user 3
+        assertEquals(Map.<String, List<OverlayInfo>>of(), mSettings.getOverlaysForUser(3));
     }
 
     @Test
     public void testRemoveUser() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_A1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_A_USER1);
 
-        assertContains(mSettings, OVERLAY_A0);
-        assertContains(mSettings, OVERLAY_B0);
-        assertContains(mSettings, OVERLAY_A1);
+        assertContains(mSettings, OVERLAY_A_USER0);
+        assertContains(mSettings, OVERLAY_B_USER0);
+        assertContains(mSettings, OVERLAY_A_USER1);
 
-        mSettings.removeUser(OVERLAY_A0.userId);
+        mSettings.removeUser(USER_0);
 
-        assertDoesNotContain(mSettings, OVERLAY_A0);
-        assertDoesNotContain(mSettings, OVERLAY_B0);
-        assertContains(mSettings, OVERLAY_A1);
+        assertDoesNotContain(mSettings, OVERLAY_A_USER0);
+        assertDoesNotContain(mSettings, OVERLAY_B_USER0);
+        assertContains(mSettings, OVERLAY_A_USER1);
     }
 
     @Test
     public void testOrderOfNewlyAddedItems() throws Exception {
         // new items are appended to the list
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
         // overlays keep their positions when updated
-        mSettings.setState(OVERLAY_B0.packageName, OVERLAY_B0.userId, STATE_ENABLED);
-        OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B0.packageName, OVERLAY_B0.userId);
+        mSettings.setState(OVERLAY_B, USER_0, STATE_ENABLED);
+        final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B, USER_0);
+        assertNotNull(oi);
 
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, oi, OVERLAY_C0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, oi, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
     }
 
     @Test
     public void testSetPriority() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        boolean changed = mSettings.setPriority(OVERLAY_B0.packageName, OVERLAY_C0.packageName,
-                OVERLAY_B0.userId);
-        assertTrue(changed);
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+        assertTrue(mSettings.setPriority(OVERLAY_B, OVERLAY_C, USER_0));
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        changed =
-            mSettings.setPriority(OVERLAY_B0.packageName, "does.not.exist", OVERLAY_B0.userId);
-        assertFalse(changed);
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+        // Nothing happens if the parent package cannot be found
+        assertFalse(mSettings.setPriority(OVERLAY_B, new OverlayIdentifier("does.not.exist"),
+                USER_0));
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        OverlayInfo otherTarget = new OverlayInfo(
+        // An overlay should not affect the priority of overlays targeting a different package
+        final OverlayInfo otherTarget = new OverlayInfo(
                 "com.test.overlay_other",
+                null,
                 "com.test.some.other.target",
                 null,
                 "some-category",
@@ -239,45 +195,36 @@
                 STATE_DISABLED,
                 0,
                 0,
-                true);
-        insert(otherTarget);
-        changed = mSettings.setPriority(OVERLAY_A0.packageName, otherTarget.packageName,
-                OVERLAY_A0.userId);
-        assertFalse(changed);
+                true,
+                false);
+        insertSetting(otherTarget);
+        assertFalse(mSettings.setPriority(OVERLAY_A, otherTarget.getOverlayIdentifier(), USER_0));
     }
 
     @Test
     public void testSetLowestPriority() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
-
-        boolean changed = mSettings.setLowestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
-        assertTrue(changed);
-
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_B0, OVERLAY_A0, OVERLAY_C0);
+        assertTrue(mSettings.setLowestPriority(OVERLAY_B, USER_0));
+        assertListsAreEqual(List.of(OVERLAY_B_USER0, OVERLAY_A_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
     }
 
     @Test
     public void testSetHighestPriority() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
-
-        boolean changed = mSettings.setHighestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
-        assertTrue(changed);
-
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+        assertTrue(mSettings.setHighestPriority(OVERLAY_B, USER_0));
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+                mSettings.getOverlaysForTarget(OVERLAY_A_USER0.targetPackageName, USER_0));
     }
 
     // tests: persist and restore
@@ -294,8 +241,8 @@
 
     @Test
     public void testPersistDifferentOverlaysSameUser() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -304,17 +251,17 @@
         assertEquals(1, countXmlTags(xml, "overlays"));
         assertEquals(2, countXmlTags(xml, "item"));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
-                    OVERLAY_A0.packageName));
+                OVERLAY_A.getPackageName()));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
-                    OVERLAY_B0.packageName));
+                OVERLAY_B.getPackageName()));
         assertEquals(2, countXmlAttributesWhere(xml, "item", "userId",
-                    Integer.toString(OVERLAY_A0.userId)));
+                    Integer.toString(USER_0)));
     }
 
     @Test
     public void testPersistSameOverlayDifferentUsers() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_A1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_A_USER1);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -323,17 +270,17 @@
         assertEquals(1, countXmlTags(xml, "overlays"));
         assertEquals(2, countXmlTags(xml, "item"));
         assertEquals(2, countXmlAttributesWhere(xml, "item", "packageName",
-                    OVERLAY_A0.packageName));
+                OVERLAY_A.getPackageName()));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
-                    Integer.toString(OVERLAY_A0.userId)));
+                    Integer.toString(USER_0)));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
-                    Integer.toString(OVERLAY_A1.userId)));
+                    Integer.toString(USER_1)));
     }
 
     @Test
     public void testPersistEnabled() throws Exception {
-        insert(OVERLAY_A0);
-        mSettings.setEnabled(OVERLAY_A0.packageName, OVERLAY_A0.userId, true);
+        insertSetting(OVERLAY_A_USER0);
+        mSettings.setEnabled(OVERLAY_A, USER_0, true);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -351,7 +298,7 @@
         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
 
         mSettings.restore(is);
-        assertDoesNotContain(mSettings, "com.test.overlay", 0);
+        assertDoesNotContain(mSettings, new OverlayIdentifier("com.test.overlay"), 0);
     }
 
     @Test
@@ -361,6 +308,7 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
                 + "<overlays version='" + version + "'>\n"
                 + "<item packageName='com.test.overlay'\n"
+                + "      overlayName='test'\n"
                 + "      userId='1234'\n"
                 + "      targetPackageName='com.test.target'\n"
                 + "      baseCodePath='/data/app/com.test.overlay-1/base.apk'\n"
@@ -373,20 +321,22 @@
         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
 
         mSettings.restore(is);
-        OverlayInfo oi = mSettings.getOverlayInfo("com.test.overlay", 1234);
+        final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test");
+        OverlayInfo oi = mSettings.getOverlayInfo(identifier, 1234);
         assertNotNull(oi);
         assertEquals("com.test.overlay", oi.packageName);
+        assertEquals("test", oi.overlayName);
         assertEquals("com.test.target", oi.targetPackageName);
         assertEquals("/data/app/com.test.overlay-1/base.apk", oi.baseCodePath);
         assertEquals(1234, oi.userId);
         assertEquals(STATE_DISABLED, oi.state);
-        assertFalse(mSettings.getEnabled("com.test.overlay", 1234));
+        assertFalse(mSettings.getEnabled(identifier, 1234));
     }
 
     @Test
     public void testPersistAndRestore() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER1);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -394,11 +344,11 @@
         OverlayManagerSettings newSettings = new OverlayManagerSettings();
         newSettings.restore(is);
 
-        OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
-        assertEquals(OVERLAY_A0, a);
+        OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A, USER_0);
+        assertEquals(OVERLAY_A_USER0, a);
 
-        OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B1.packageName, OVERLAY_B1.userId);
-        assertEquals(OVERLAY_B1, b);
+        OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B, USER_1);
+        assertEquals(OVERLAY_B_USER1, b);
     }
 
     private int countXmlTags(InputStream in, String tagToLookFor) throws Exception {
@@ -433,43 +383,53 @@
         return count;
     }
 
-    private void insert(OverlayInfo oi) throws Exception {
-        mSettings.init(oi.packageName, oi.userId, oi.targetPackageName, null, oi.baseCodePath,
-                true, false,0, oi.category);
-        mSettings.setState(oi.packageName, oi.userId, oi.state);
-        mSettings.setEnabled(oi.packageName, oi.userId, false);
+    private void insertSetting(OverlayInfo oi) throws Exception {
+        mSettings.init(oi.getOverlayIdentifier(), oi.userId, oi.targetPackageName, null,
+                oi.baseCodePath, true, false,0, oi.category, oi.isFabricated);
+        mSettings.setState(oi.getOverlayIdentifier(), oi.userId, oi.state);
+        mSettings.setEnabled(oi.getOverlayIdentifier(), oi.userId, false);
     }
 
     private static void assertContains(final OverlayManagerSettings settings,
             final OverlayInfo oi) {
-        assertContains(settings, oi.packageName, oi.userId);
-    }
-
-    private static void assertContains(final OverlayManagerSettings settings,
-            final String packageName, int userId) {
         try {
-            settings.getOverlayInfo(packageName, userId);
+            settings.getOverlayInfo(oi.getOverlayIdentifier(), oi.userId);
         } catch (OverlayManagerSettings.BadKeyException e) {
-            fail(String.format("settings does not contain packageName=%s userId=%d",
-                        packageName, userId));
+            fail(String.format("settings does not contain overlay=%s userId=%d",
+                    oi.getOverlayIdentifier(), oi.userId));
         }
     }
 
     private static void assertDoesNotContain(final OverlayManagerSettings settings,
             final OverlayInfo oi) {
-        assertDoesNotContain(settings, oi.packageName, oi.userId);
+        assertDoesNotContain(settings, oi.getOverlayIdentifier(), oi.userId);
     }
 
     private static void assertDoesNotContain(final OverlayManagerSettings settings,
-            final String packageName, int userId) {
+            final OverlayIdentifier overlay, int userId) {
         try {
-            settings.getOverlayInfo(packageName, userId);
-            fail(String.format("settings contains packageName=%s userId=%d", packageName, userId));
+            settings.getOverlayInfo(overlay, userId);
+            fail(String.format("settings contains overlay=%s userId=%d", overlay, userId));
         } catch (OverlayManagerSettings.BadKeyException e) {
             // do nothing: we expect to end up here
         }
     }
 
+    private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId) {
+        return new OverlayInfo(
+                identifier.getPackageName(),
+                identifier.getOverlayName(),
+                "com.test.target",
+                null,
+                "some-category",
+                "/data/app/" + identifier + "/base.apk",
+                STATE_DISABLED,
+                userId,
+                0,
+                true,
+                false);
+    }
+
     private static void assertContains(int[] haystack, int needle) {
         List<Integer> list = IntStream.of(haystack)
                 .boxed()
@@ -490,16 +450,11 @@
         }
     }
 
-    private static void assertListsAreEqual(List<OverlayInfo> list, OverlayInfo... array) {
-        List<OverlayInfo> other = Stream.of(array)
-                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
-        assertListsAreEqual(list, other);
-    }
-
-    private static void assertListsAreEqual(List<OverlayInfo> list, List<OverlayInfo> other) {
-        if (!list.equals(other)) {
+    private static void assertListsAreEqual(
+            @NonNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual) {
+        if (!expected.equals(actual)) {
             fail(String.format("lists [%s] and [%s] differ",
-                        TextUtils.join(",", list), TextUtils.join(",", other)));
+                        TextUtils.join(",", expected), TextUtils.join(",", actual)));
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index 22020ad..bc84e35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -96,7 +96,7 @@
 
         int[] reasons = new int[] {
                 PackageManagerService.REASON_FIRST_BOOT,
-                PackageManagerService.REASON_BOOT,
+                PackageManagerService.REASON_POST_BOOT,
                 PackageManagerService.REASON_INSTALL,
                 PackageManagerService.REASON_BACKGROUND_DEXOPT,
                 PackageManagerService.REASON_AB_OTA,
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
new file mode 100644
index 0000000..70d85b6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.pm.parsing.library;
+
+import android.os.Build;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link AndroidNetIpSecIkeUpdater}
+ */
+@Presubmit
+@SmallTest
+@RunWith(JUnit4.class)
+public class AndroidNetIpSecIkeUpdaterTest extends PackageSharedLibraryUpdaterTest {
+
+    @Test
+    public void otherUsesLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.O)
+                .addUsesLibrary("other")
+                .addUsesOptionalLibrary("optional")
+                .addUsesLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.O)
+                .addUsesLibrary("other")
+                .addUsesOptionalLibrary("optional")
+                .hideAsParsed())
+                .hideAsFinal();
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void in_usesLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .addUsesLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void in_usesOptionalLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .addUsesOptionalLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
+        checkBackwardsCompatibility(before, after, AndroidNetIpSecIkeUpdater::new);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
index 09c8142..9768f17 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
@@ -165,6 +165,23 @@
         checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
     }
 
+    /**
+     * Ensures that the {@link PackageBackwardCompatibility} uses a
+     * {@link AndroidNetIpSecIkeUpdater}.
+     */
+    @Test
+    public void android_net_ipsec_ike_in_usesLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .addUsesLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+
+        ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+    }
+
     private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
         checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 21fd04e..925b6f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -29,6 +29,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -59,6 +60,10 @@
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -66,6 +71,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
 import android.util.SparseBooleanArray;
+import android.view.Surface;
+import android.window.TaskSnapshot;
 
 import androidx.test.filters.MediumTest;
 
@@ -446,12 +453,8 @@
         doReturn(false).when(child2).isOrganized();
         mRecentTasks.add(root);
 
-        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
-        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-        final List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0 /* callingUid */).getList();
-
         // Make sure only organized child will be appended.
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
         final List<RecentTaskInfo> childrenTaskInfos = infos.get(0).childrenTaskInfos;
         assertEquals(childrenTaskInfos.size(), 1);
         assertEquals(childrenTaskInfos.get(0).taskId, child1.mTaskId);
@@ -1051,9 +1054,6 @@
 
     @Test
     public void testTaskInfo_expectNoExtras() {
-        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
-        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-
         final Bundle data = new Bundle();
         data.putInt("key", 100);
         final Task task1 = createTaskBuilder(".Task").build();
@@ -1063,8 +1063,7 @@
                 .build();
         mRecentTasks.add(r1.getTask());
 
-        final List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList();
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
         assertTrue(infos.size() == 1);
         for (int i = 0; i < infos.size(); i++)  {
             final Bundle extras = infos.get(i).baseIntent.getExtras();
@@ -1072,6 +1071,60 @@
         }
     }
 
+    @Test
+    public void testLastSnapshotData_snapshotSaved() {
+        final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), new Point(80, 80));
+        final Task task1 = createTaskBuilder(".Task").build();
+        task1.onSnapshotChanged(snapshot);
+
+        mRecentTasks.add(task1);
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+        final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+                infos.get(0).lastSnapshotData;
+        assertTrue(lastSnapshotData.taskSize.equals(100, 100));
+        assertTrue(lastSnapshotData.bufferSize.equals(80, 80));
+    }
+
+    @Test
+    public void testLastSnapshotData_noBuffer() {
+        final Task task1 = createTaskBuilder(".Task").build();
+        final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), null);
+        task1.onSnapshotChanged(snapshot);
+
+        mRecentTasks.add(task1);
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+        final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+                infos.get(0).lastSnapshotData;
+        assertTrue(lastSnapshotData.taskSize.equals(100, 100));
+        assertNull(lastSnapshotData.bufferSize);
+    }
+
+    @Test
+    public void testLastSnapshotData_notSet() {
+        final Task task1 = createTaskBuilder(".Task").build();
+
+        mRecentTasks.add(task1);
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+        final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+                infos.get(0).lastSnapshotData;
+        assertNull(lastSnapshotData.taskSize);
+        assertNull(lastSnapshotData.bufferSize);
+    }
+
+    private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
+        HardwareBuffer buffer = null;
+        if (bufferSize != null) {
+            buffer = mock(HardwareBuffer.class);
+            doReturn(bufferSize.x).when(buffer).getWidth();
+            doReturn(bufferSize.y).when(buffer).getHeight();
+        }
+        return new TaskSnapshot(1, new ComponentName("", ""), buffer,
+                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+                Surface.ROTATION_0, taskSize, new Rect() /* insets */, false /* isLowResolution */,
+                true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
+                false /* isTranslucent */, false /* hasImeSurface */);
+    }
+
     /**
      * Ensures that the raw recent tasks list is in the provided order. Note that the expected tasks
      * should be ordered from least to most recent.
@@ -1084,15 +1137,19 @@
         }
     }
 
+    private List<RecentTaskInfo> getRecentTasks(int flags) {
+        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
+        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
+        return mRecentTasks.getRecentTasks(MAX_VALUE, flags, true /* getTasksAllowed */,
+                TEST_USER_0_ID, 0 /* callingUid */).getList();
+    }
+
     /**
      * Ensures that the recent tasks list is in the provided order. Note that the expected tasks
      * should be ordered from least to most recent.
      */
     private void assertGetRecentTasksOrder(int getRecentTaskFlags, Task... expectedTasks) {
-        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
-        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-        List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, getRecentTaskFlags,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList();
+        List<RecentTaskInfo> infos = getRecentTasks(getRecentTaskFlags);
         assertTrue(expectedTasks.length == infos.size());
         for (int i = 0; i < infos.size(); i++)  {
             assertTrue(expectedTasks[i].mTaskId == infos.get(i).taskId);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 0cb1255..9e419d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1526,15 +1526,19 @@
 
         @Override
         public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
-                long endTime, String callingPackage) {
+                long endTime, String callingPackage, int userId) {
             if (!hasPermission(callingPackage)) {
                 return null;
             }
 
-            final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
-                    Binder.getCallingUid(), UserHandle.getCallingUserId());
+            final int callingUid = Binder.getCallingUid();
+            userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
+                    userId, false, true, "queryUsageStats", callingPackage);
 
-            final int userId = UserHandle.getCallingUserId();
+            // Check the caller's userId for obfuscation decision, not the user being queried
+            final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
+                    callingUid, UserHandle.getCallingUserId());
+
             final long token = Binder.clearCallingIdentity();
             try {
                 final List<UsageStats> results = UsageStatsService.this.queryUsageStats(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index be36f82..f687e4b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -581,17 +581,24 @@
             }
         }
 
-        VoiceInteractionServiceInfo findAvailInteractor(int userHandle, String packageName) {
-            List<ResolveInfo> available =
-                    mContext.getPackageManager().queryIntentServicesAsUser(
-                            new Intent(VoiceInteractionService.SERVICE_INTERFACE)
-                                    .setPackage(packageName),
-                            PackageManager.GET_META_DATA
-                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+        private List<ResolveInfo> queryInteractorServices(
+                @UserIdInt int user,
+                @Nullable String packageName) {
+            return mContext.getPackageManager().queryIntentServicesAsUser(
+                    new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(packageName),
+                    PackageManager.GET_META_DATA
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                    user);
+        }
+
+        VoiceInteractionServiceInfo findAvailInteractor(
+                @UserIdInt int user,
+                @Nullable String packageName) {
+            List<ResolveInfo> available = queryInteractorServices(user, packageName);
             int numAvailable = available.size();
             if (numAvailable == 0) {
-                Slog.w(TAG, "no available voice interaction services found for user " + userHandle);
+                Slog.w(TAG, "no available voice interaction services found for user " + user);
                 return null;
             }
             // Find first system package.  We never want to allow third party services to
@@ -1643,13 +1650,7 @@
                     String pkg = roleHolders.get(0);
 
                     // Try to set role holder as VoiceInteractionService
-                    List<ResolveInfo> services = mPm.queryIntentServicesAsUser(
-                            new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg),
-                            PackageManager.GET_META_DATA
-                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
-
-                    for (ResolveInfo resolveInfo : services) {
+                    for (ResolveInfo resolveInfo : queryInteractorServices(userId, pkg)) {
                         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
 
                         VoiceInteractionServiceInfo voiceInteractionServiceInfo =
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3b06fd3..170ed3e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2024,7 +2024,7 @@
         boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
                 TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
         boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
-                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
                         + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
                         + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ac584c1..21cf3e5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4748,6 +4748,21 @@
     public static final String KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL =
             "network_temp_not_metered_supported_bool";
 
+    /*
+     * Boolean indicating whether the SIM PIN can be stored and verified
+     * seamlessly after an unattended reboot.
+     *
+     * The device configuration value {@code config_allow_pin_storage_for_unattended_reboot}
+     * ultimately controls whether this carrier configuration option is used.  Where
+     * {@code config_allow_pin_storage_for_unattended_reboot} is false, the value of the
+     * {@link #KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL} carrier configuration option is
+     * ignored.
+     *
+     * @hide
+     */
+    public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL =
+            "store_sim_pin_for_unattended_reboot_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5304,6 +5319,7 @@
         sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
         sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
         sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
+        sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 65b8de2..16ffd9e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11255,16 +11255,21 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
-     * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+     * Additionally, depending on the level of location permissions the caller holds (i.e. no
+     * location permissions, {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}, or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}), location-sensitive fields will
+     * be cleared from the return value.
+     *
+     * <p>Note also that if the caller holds any sort of location permission, a usage event for the
+     * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION} or
+     * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION}
+     * will be logged against the caller when calling this method.
      *
      * May return {@code null} when the subscription is inactive or when there was an error
      * communicating with the phone process.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
-    @RequiresPermission(allOf = {
-            Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.ACCESS_COARSE_LOCATION
-    })
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public @Nullable ServiceState getServiceState() {
         return getServiceStateForSubscriber(getSubId());
     }
@@ -14994,6 +14999,7 @@
         }
         return THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR;
     }
+
     /**
      * Registers a listener object to receive notification of changes
      * in specified telephony states.
@@ -15350,4 +15356,66 @@
             return PhoneCapability.DEFAULT_SSSS_CAPABILITY;
         }
     }
+
+    /**
+     * The unattended reboot was prepared successfully.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0;
+
+    /**
+     * The unattended reboot was prepared, but the user will need to manually
+     * enter the PIN code of at least one SIM card present in the device.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1;
+
+    /**
+     * The unattended reboot was not prepared due to generic error.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"PREPARE_UNATTENDED_REBOOT_"},
+            value = {
+                    PREPARE_UNATTENDED_REBOOT_SUCCESS,
+                    PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED,
+                    PREPARE_UNATTENDED_REBOOT_ERROR
+            })
+    public @interface PrepareUnattendedRebootResult {}
+
+    /**
+     * Prepare TelephonyManager for an unattended reboot. The reboot is required to be done
+     * shortly (e.g. within 15 seconds) after the API is invoked.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#REBOOT}
+     *
+     * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success.
+     * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains
+     * at least one SIM card for which the user needs to manually enter the PIN
+     * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
+     * of error.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.REBOOT)
+    @PrepareUnattendedRebootResult
+    public int prepareForUnattendedReboot() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.prepareForUnattendedReboot();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Telephony#prepareForUnattendedReboot RemoteException", e);
+            e.rethrowFromSystemServer();
+        }
+        return PREPARE_UNATTENDED_REBOOT_ERROR;
+    }
 }
diff --git a/tools/hiddenapi/Android.bp b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
similarity index 68%
rename from tools/hiddenapi/Android.bp
rename to telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
index e0eb06cb..0830ff2 100644
--- a/tools/hiddenapi/Android.bp
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (c) 2021 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.
@@ -14,17 +14,6 @@
  * limitations under the License.
  */
 
-python_binary_host {
-	name: "merge_csv",
-	main: "merge_csv.py",
-	srcs: ["merge_csv.py"],
-	version: {
-		py2: {
-			enabled: false,
-		},
-		py3: {
-			enabled: true,
-			embedded_launcher: true
-		},
-	},
-}
+package android.telephony.ims;
+
+parcelable ImsRegistrationAttributes;
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
new file mode 100644
index 0000000..ccb3231
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Contains the attributes associated with the current IMS registration.
+ */
+public final class ImsRegistrationAttributes implements Parcelable {
+
+    /**
+     * Attribute to specify if an EPDG tunnel is setup over the cellular internet APN.
+     * <p>
+     * If IMS is registered through an EPDG tunnel is setup over the cellular internet APN then this
+     * bit will be set. If IMS is registered through the IMS APN, then this bit will not be set.
+     *
+     */
+    public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1 << 0;
+
+    /** @hide */
+    // Defines the underlying radio technology type that we have registered for IMS over.
+    @IntDef(prefix = "ATTR_",
+            value = {
+                    ATTR_EPDG_OVER_CELL_INTERNET,
+            },
+            flag = true)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ImsAttributeFlag {}
+
+    /**
+     * Builder for creating {@link ImsRegistrationAttributes} instances.
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        private final int mRegistrationTech;
+        private Set<String> mFeatureTags = Collections.emptySet();
+
+        /**
+         * Build a new instance of {@link ImsRegistrationAttributes}.
+         *
+         * @param registrationTech The Radio Access Technology that IMS is registered on.
+         */
+        public Builder(@ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) {
+            mRegistrationTech = registrationTech;
+        }
+
+        /**
+         * Optional IMS feature tags included in this IMS registration.
+         * @param tags A set of Strings containing the MMTEL and RCS feature tags associated with
+         *         the IMS registration. This information is used for services such as the UCE
+         *         service to ascertain the complete IMS registration state to ensure the SIP
+         *         PUBLISH is accurate. The format of the set of feature tags must be one feature
+         *         tag key and value per entry. Each feature tag will contain the feature tag name
+         *         and string value (if applicable), even if they have the same feature tag name.
+         *         For example,
+         *         {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg,
+         *         urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} must
+         *         be split into three feature tag entries:
+         *         {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg",
+         *         +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session",
+         *         +g.gsma.callcomposer}}.
+         */
+        public @NonNull Builder setFeatureTags(@NonNull Set<String> tags) {
+            if (tags == null) {
+                throw new IllegalArgumentException("feature tag set must not be null");
+            }
+            mFeatureTags = new ArraySet<>(tags);
+            return this;
+        }
+
+        /**
+         * @return A new instance created from this builder.
+         */
+        public @NonNull ImsRegistrationAttributes build() {
+            return new ImsRegistrationAttributes(mRegistrationTech,
+                    RegistrationManager.getAccessType(mRegistrationTech),
+                    getAttributeFlags(mRegistrationTech),
+                    mFeatureTags);
+        }
+
+        /**
+         * @return attribute flags from the registration technology.
+         */
+        private static int getAttributeFlags(int imsRadioTech) {
+            int attributes = 0;
+            if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
+                attributes |= ATTR_EPDG_OVER_CELL_INTERNET;
+            }
+            return attributes;
+        }
+    }
+
+    private final int mRegistrationTech;
+    private final int mTransportType;
+    private final int mImsAttributeFlags;
+    private final ArrayList<String> mFeatureTags;
+
+    /**
+     * Create a new {@link ImsRegistrationAttributes} instance.
+     *
+     * @param registrationTech The technology that IMS has been registered on.
+     * @param transportType The transport type that IMS has been registered on.
+     * @param imsAttributeFlags The attributes associated with the IMS registration.
+     * @param featureTags The feature tags included in the IMS registration.
+     * @see Builder
+     * @hide
+     */
+    public ImsRegistrationAttributes(
+            @ImsRegistrationImplBase.ImsRegistrationTech int registrationTech,
+            @AccessNetworkConstants.TransportType int transportType,
+            @ImsAttributeFlag int imsAttributeFlags,
+            @Nullable Set<String> featureTags) {
+        mRegistrationTech = registrationTech;
+        mTransportType = transportType;
+        mImsAttributeFlags = imsAttributeFlags;
+        mFeatureTags = new ArrayList<>(featureTags);
+    }
+
+    /**@hide*/
+    public ImsRegistrationAttributes(Parcel source) {
+        mRegistrationTech = source.readInt();
+        mTransportType = source.readInt();
+        mImsAttributeFlags = source.readInt();
+        mFeatureTags = new ArrayList<>();
+        source.readList(mFeatureTags, null /*classloader*/);
+    }
+
+    /**
+     * @return The Radio Access Technology that the IMS registration has been registered over.
+     * @hide
+     */
+    @SystemApi
+    public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTechnology() {
+        return mRegistrationTech;
+    }
+
+    /**
+     * @return The access network transport type that IMS has been registered over.
+     */
+    public @AccessNetworkConstants.TransportType int  getTransportType() {
+        return mTransportType;
+    }
+
+    /**
+     * @return A bit-mask containing attributes associated with the IMS registration.
+     */
+    public @ImsAttributeFlag int getAttributeFlags() {
+        return mImsAttributeFlags;
+    }
+
+    /**
+     * Gets the Set of feature tags associated with the current IMS registration, if the IMS
+     * service supports supplying this information.
+     * <p>
+     * The format of the set of feature tags will be one feature tag key and value per entry and
+     * will potentially contain MMTEL and RCS feature tags, depending the configuration of the IMS
+     * service associated with the registration indications. Each feature tag will contain the
+     * feature tag name and string value (if applicable), even if they have the same feature tag
+     * name. For example, {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg,
+     * urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} will be split
+     * into three feature tag  entries:
+     * {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg",
+     * +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session",
+     * +g.gsma.callcomposer}}.
+     * @return The Set of feature tags associated with the current IMS registration.
+     */
+    public @NonNull Set<String> getFeatureTags() {
+        if (mFeatureTags == null) {
+            return Collections.emptySet();
+        }
+        return new ArraySet<>(mFeatureTags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mRegistrationTech);
+        dest.writeInt(mTransportType);
+        dest.writeInt(mImsAttributeFlags);
+        dest.writeList(mFeatureTags);
+    }
+
+    public static final @NonNull Creator<ImsRegistrationAttributes> CREATOR =
+            new Creator<ImsRegistrationAttributes>() {
+                @Override
+                public ImsRegistrationAttributes createFromParcel(Parcel source) {
+                    return new ImsRegistrationAttributes(source);
+                }
+
+                @Override
+                public ImsRegistrationAttributes[] newArray(int size) {
+                    return new ImsRegistrationAttributes[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ImsRegistrationAttributes that = (ImsRegistrationAttributes) o;
+        return mRegistrationTech == that.mRegistrationTech
+                && mTransportType == that.mTransportType
+                && mImsAttributeFlags == that.mImsAttributeFlags
+                && Objects.equals(mFeatureTags, that.mFeatureTags);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRegistrationTech, mTransportType, mImsAttributeFlags, mFeatureTags);
+    }
+
+    @Override
+    public String toString() {
+        return "ImsRegistrationAttributes { transportType= " + mTransportType + ", attributeFlags="
+                + mImsAttributeFlags + ", featureTags=[" + mFeatureTags + "]}";
+    }
+}
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index b430bef..c682afe 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -72,30 +72,6 @@
      */
     int REGISTRATION_STATE_REGISTERED = 2;
 
-    /**
-     * @hide
-     */
-    // Defines the underlying radio technology type that we have registered for IMS over.
-    @IntDef(prefix = "ATTR_",
-            value = {
-                    ATTR_EPDG_OVER_CELL_INTERNET,
-            },
-            flag = true)
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ImsAttributes {}
-
-    /**
-     * Attribute to specify if EPDG tunnel is setup over cellular internet.
-     * if EPDG tunnel is setup over cellular internet then this bit will be set else the same will
-     * not be set.
-     */
-    int ATTR_EPDG_OVER_CELL_INTERNET = 0x00000001;
-
-    //******************************************************************************************
-    // Next attribute value: 0x00000002
-    //******************************************************************************************
-
-
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
     // and WWAN are more accurate constants.
@@ -103,7 +79,8 @@
             new HashMap<Integer, Integer>() {{
                 // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
                 // case, since it is defined.
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, -1);
+                put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+                        AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
                 put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
                 put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
@@ -132,6 +109,20 @@
     }
 
     /**
+     * @param regtech The registration technology.
+     * @return The Access Network type from registration technology.
+     * @hide
+     */
+    static int getAccessType(int regtech) {
+        if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regtech)) {
+            Log.w("RegistrationManager", "getAccessType - invalid regType returned: "
+                    + regtech);
+            return AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+        }
+        return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regtech);
+    }
+
+    /**
      * Callback class for receiving IMS network Registration callback events.
      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
      * @see #unregisterImsRegistrationCallback(RegistrationCallback)
@@ -149,45 +140,24 @@
             }
 
             @Override
-            public void onRegistered(int imsRadioTech) {
+            public void onRegistered(ImsRegistrationAttributes attr) {
                 if (mLocalCallback == null) return;
 
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() -> {
-                        mLocalCallback.onRegistered(getAccessType(imsRadioTech));
-                    });
-                    int attributes = 0;
-                    if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                        attributes = changeBitmask(attributes, ATTR_EPDG_OVER_CELL_INTERNET,
-                                true);
-                    }
-                    final int finalattributes = attributes;
-                    mExecutor.execute(() ->
-                            mLocalCallback.onRegistered(getAccessType(imsRadioTech),
-                                    finalattributes));
+                    mExecutor.execute(() -> mLocalCallback.onRegistered(attr));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
             }
 
             @Override
-            public void onRegistering(int imsRadioTech) {
+            public void onRegistering(ImsRegistrationAttributes attr) {
                 if (mLocalCallback == null) return;
 
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() ->
-                            mLocalCallback.onRegistering(getAccessType(imsRadioTech)));
-                    int attributes = 0;
-                    if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                        attributes = changeBitmask(attributes, ATTR_EPDG_OVER_CELL_INTERNET,
-                                true);
-                    }
-                    final int finalattributes = attributes;
-                    mExecutor.execute(() ->
-                            mLocalCallback.onRegistering(getAccessType(imsRadioTech),
-                                    finalattributes));
+                    mExecutor.execute(() -> mLocalCallback.onRegistering(attr));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -232,31 +202,6 @@
             private void setExecutor(Executor executor) {
                 mExecutor = executor;
             }
-
-            private static int getAccessType(int regType) {
-                if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regType)) {
-                    Log.w("RegistrationManager", "RegistrationBinder - invalid regType returned: "
-                            + regType);
-                    return -1;
-                }
-                return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regType);
-            }
-
-            /**
-             * Changes a attribute bit-mask to add or remove an attribute.
-             *
-             * @param bitmask The bit-mask.
-             * @param bitfield The bit-field to change.
-             * @param enabled Whether the bit-field should be set or removed.
-             * @return The bit-mask with the bit-field changed.
-             */
-            private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
-                if (enabled) {
-                    return bitmask | bitfield;
-                } else {
-                    return bitmask & ~bitfield;
-                }
-            }
         }
 
         private final RegistrationBinder mBinder = new RegistrationBinder(this);
@@ -265,7 +210,7 @@
          * Notifies the framework when the IMS Provider is registered to the IMS network.
          *
          * @param imsTransportType the radio access technology.
-         * @deprecated Use {@link #onRegistered(int, int)} instead.
+         * @deprecated Use {@link #onRegistered(ImsRegistrationAttributes)} instead.
          */
         @Deprecated
         public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) {
@@ -273,25 +218,20 @@
 
         /**
          * Notifies the framework when the IMS Provider is registered to the IMS network
-         * with corresponding attributes
+         * with corresponding attributes.
          *
-         * @param imsTransportType the radio access technology.
-         * @param registrationAttributes IMS registration attributes as a bitmap of attributes.
-         * Possible attributes are following
-         * <ul>
-         *     <li>{@link #ATTR_EPDG_OVER_CELL_INTERNET}</li>
-         * </ul>
-         *
+         * @param attributes The attributes associated with this IMS registration.
          */
-        public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType,
-                @ImsAttributes int registrationAttributes) {
+        public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+            // Default impl to keep backwards compatibility with old implementations
+            onRegistered(attributes.getTransportType());
         }
 
         /**
          * Notifies the framework when the IMS Provider is trying to register the IMS network.
          *
          * @param imsTransportType the radio access technology.
-         * @deprecated Use {@link #onRegistering(int, int)} instead.
+         * @deprecated Use {@link #onRegistering(ImsRegistrationAttributes)} instead.
          */
         public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) {
         }
@@ -299,12 +239,11 @@
         /**
          * Notifies the framework when the IMS Provider is trying to register the IMS network.
          *
-         * @param imsTransportType the radio access technology.
-         * @param registrationAttributes IMS registration attributes as a bitmap of attributes.
-         * Possible attributes are following
+         * @param attributes The attributes associated with this IMS registration.
          */
-        public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType,
-                @ImsAttributes int registrationAttributes) {
+        public void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
+            // Default impl to keep backwards compatibility with old implementations
+            onRegistering(attributes.getTransportType());
         }
 
         /**
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
index 749b191..179407c 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
@@ -21,6 +21,7 @@
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 
 /**
  * See {@link ImsManager#RegistrationCallback} for more information.
@@ -28,8 +29,8 @@
  * {@hide}
  */
 oneway interface IImsRegistrationCallback {
-   void onRegistered(int imsRadioTech);
-   void onRegistering(int imsRadioTech);
+   void onRegistered(in ImsRegistrationAttributes attr);
+   void onRegistering(in ImsRegistrationAttributes attr);
    void onDeregistered(in ImsReasonInfo info);
    void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
    void onSubscriberAssociatedUriChanged(in Uri[] uris);
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 4f753c3..39994be 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -18,17 +18,18 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.RemoteCallbackListExt;
 import com.android.internal.util.ArrayUtils;
 
@@ -89,7 +90,10 @@
 
         @Override
         public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
-            return getConnectionType();
+            synchronized (mLock) {
+                return (mRegistrationAttributes == null) ? REGISTRATION_TECH_NONE
+                        : mRegistrationAttributes.getRegistrationTechnology();
+            }
         }
 
         @Override
@@ -122,8 +126,7 @@
             new RemoteCallbackListExt<>();
     private final Object mLock = new Object();
     // Locked on mLock
-    private @ImsRegistrationTech
-    int mConnectionType = REGISTRATION_TECH_NONE;
+    private ImsRegistrationAttributes mRegistrationAttributes;
     // Locked on mLock
     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
     // Locked on mLock, create unspecified disconnect cause.
@@ -201,18 +204,24 @@
     /**
      * Notify the framework that the device is connected to the IMS network.
      *
-     * @param imsRadioTech the radio access technology. Valid values are defined as
-     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
-     * {@link #REGISTRATION_TECH_CROSS_SIM}.
+     * @param imsRadioTech the radio access technology.
      */
     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
-        updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED);
+        onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
+    }
+
+    /**
+     * Notify the framework that the device is connected to the IMS network.
+     *
+     * @param attributes The attributes associated with the IMS registration.
+     */
+    public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+        updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onRegistered(imsRadioTech);
+                c.onRegistered(attributes);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationConnected() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback.");
             }
         });
     }
@@ -220,18 +229,24 @@
     /**
      * Notify the framework that the device is trying to connect the IMS network.
      *
-     * @param imsRadioTech the radio access technology. Valid values are defined as
-     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
-     * {@link #REGISTRATION_TECH_CROSS_SIM}.
+     * @param imsRadioTech the radio access technology.
      */
     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
-        updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING);
+        onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
+    }
+
+    /**
+     * Notify the framework that the device is trying to connect the IMS network.
+     *
+     * @param attributes The attributes associated with the IMS registration.
+     */
+    public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
+        updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onRegistering(imsRadioTech);
+                c.onRegistering(attributes);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationProcessing() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback.");
             }
         });
     }
@@ -260,8 +275,7 @@
             try {
                 c.onDeregistered(reasonInfo);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
             }
         });
     }
@@ -281,8 +295,7 @@
             try {
                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback.");
             }
         });
     }
@@ -306,14 +319,13 @@
         try {
             callback.onSubscriberAssociatedUriChanged(uris);
         } catch (RemoteException e) {
-            Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping "
-                    + "callback.");
+            Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback.");
         }
     }
 
-    private void updateToState(@ImsRegistrationTech int connType, int newState) {
+    private void updateToState(ImsRegistrationAttributes attributes, int newState) {
         synchronized (mLock) {
-            mConnectionType = connType;
+            mRegistrationAttributes = attributes;
             mRegistrationState = newState;
             mLastDisconnectCause = null;
         }
@@ -325,7 +337,7 @@
             mUrisSet = false;
             mUris = null;
 
-            updateToState(REGISTRATION_TECH_NONE,
+            updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(),
                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
             if (info != null) {
                 mLastDisconnectCause = info;
@@ -337,30 +349,19 @@
     }
 
     /**
-     * @return the current registration connection type. Valid values are
-     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
-     * {@link #REGISTRATION_TECH_CROSS_SIM}.
-     * @hide
-     */
-    @VisibleForTesting
-    public final @ImsRegistrationTech int getConnectionType() {
-        synchronized (mLock) {
-            return mConnectionType;
-        }
-    }
-
-    /**
      * @param c the newly registered callback that will be updated with the current registration
      *         state.
      */
     private void updateNewCallbackWithState(IImsRegistrationCallback c)
             throws RemoteException {
         int state;
+        ImsRegistrationAttributes attributes;
         ImsReasonInfo disconnectInfo;
         boolean urisSet;
         Uri[] uris;
         synchronized (mLock) {
             state = mRegistrationState;
+            attributes = mRegistrationAttributes;
             disconnectInfo = mLastDisconnectCause;
             urisSet = mUrisSet;
             uris = mUris;
@@ -371,11 +372,11 @@
                 break;
             }
             case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
-                c.onRegistering(getConnectionType());
+                c.onRegistering(attributes);
                 break;
             }
             case RegistrationManager.REGISTRATION_STATE_REGISTERED: {
-                c.onRegistered(getConnectionType());
+                c.onRegistered(attributes);
                 break;
             }
             case REGISTRATION_STATE_UNKNOWN: {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0cd17da3..1d04953 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2387,4 +2387,18 @@
      * Gets the current phone capability.
      */
     PhoneCapability getPhoneCapability();
+
+    /**
+     * Prepare TelephonyManager for an unattended reboot. The reboot is
+     * required to be done shortly after the API is invoked.
+     *
+     * Requires system privileges.
+     *
+     * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success.
+     * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains
+     * at least one SIM card for which the user needs to manually enter the PIN
+     * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
+     * of error.
+     */
+    int prepareForUnattendedReboot();
 }
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 39dc9c2..e2d2eca 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,7 +16,10 @@
     name: "ApkVerityTest",
     srcs: ["src/**/*.java"],
     libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
-    static_libs: ["frameworks-base-hostutils"],
+    static_libs: [
+        "block_device_writer_jar",
+        "frameworks-base-hostutils",
+    ],
     test_suites: ["general-tests", "vts"],
     target_required: [
         "block_device_writer_module",
diff --git a/tests/ApkVerityTest/OWNERS b/tests/ApkVerityTest/OWNERS
new file mode 100644
index 0000000..d67285ed
--- /dev/null
+++ b/tests/ApkVerityTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+victorhsieh@google.com
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 37fbc29..8f2d4bc 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -51,3 +51,9 @@
     test_suites: ["general-tests", "pts", "vts"],
     gtest: false,
 }
+
+java_library_host {
+    name: "block_device_writer_jar",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed", "junit"],
+}
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
new file mode 100644
index 0000000..5c2c15b
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 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.blockdevicewriter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper for block_device_writer command.
+ *
+ * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
+ * 1. In Android.bp, add:
+ * <pre>
+ *     target_required: ["block_device_writer_module"],
+ * </pre>
+ * 2. In AndroidText.xml, add:
+ * <pre>
+ *     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ *         <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+ *     </target_preparer>
+ * </pre>
+ */
+public final class BlockDeviceWriter {
+    private static final String EXECUTABLE = "/data/local/tmp/block_device_writer";
+
+    /**
+     * Modifies a byte of the file directly against the backing block storage.
+     *
+     * The effect can only be observed when the page cache is read from disk again. See
+     * {@link #dropCaches} for details.
+     */
+    public static void damageFileAgainstBlockDevice(ITestDevice device, String path,
+            long offsetOfTargetingByte)
+            throws DeviceNotAvailableException {
+        assertThat(path).startsWith("/data/");
+        ITestDevice.MountPointInfo mountPoint = device.getMountPointInfo("/data");
+        ArrayList<String> args = new ArrayList<>();
+        args.add(EXECUTABLE);
+        if ("f2fs".equals(mountPoint.type)) {
+            args.add("--use-f2fs-pinning");
+        }
+        args.add(mountPoint.filesystem);
+        args.add(path);
+        args.add(Long.toString(offsetOfTargetingByte));
+        CommandResult result = device.executeShellV2Command(String.join(" ", args));
+        assertWithMessage(
+                String.format("stdout=%s\nstderr=%s", result.getStdout(), result.getStderr()))
+                .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+    }
+
+    /**
+     * Drops file caches so that the result of {@link #damageFileAgainstBlockDevice} can be
+     * observed. If a process has an open FD or memory map of the damaged file, cache eviction won't
+     * happen and the damage cannot be observed.
+     */
+    public static void dropCaches(ITestDevice device) throws DeviceNotAvailableException {
+        CommandResult result = device.executeShellV2Command(
+                "sync && echo 1 > /proc/sys/vm/drop_caches");
+        assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+    }
+
+    public static void assertFileNotOpen(ITestDevice device, String path)
+            throws DeviceNotAvailableException {
+        CommandResult result = device.executeShellV2Command("lsof " + path);
+        assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+        assertThat(result.getStdout()).isEmpty();
+    }
+
+    /**
+     * Checks if the give offset of a file can be read.
+     * This method will return false if the file has fs-verity enabled and is damaged at the offset.
+     */
+    public static boolean canReadByte(ITestDevice device, String filePath, long offset)
+            throws DeviceNotAvailableException {
+        CommandResult result = device.executeShellV2Command(
+                "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
+        return result.getStatus() == CommandStatus.SUCCESS;
+    }
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index d0eb9be..ab3572b 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -24,6 +24,7 @@
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.blockdevicewriter.BlockDeviceWriter;
 import com.android.fsverity.AddFsVerityCertRule;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -334,22 +335,23 @@
         long offsetFirstByte = 0;
 
         // The first two pages should be both readable at first.
-        assertTrue(canReadByte(apkPath, offsetFirstByte));
+        assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
         if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
-            assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+                    offsetFirstByte + FSVERITY_PAGE_SIZE));
         }
 
         // Damage the file directly against the block device.
         damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
 
         // Expect actual read from disk to fail but only at damaged page.
-        dropCaches();
-        assertFalse(canReadByte(apkPath, offsetFirstByte));
+        BlockDeviceWriter.dropCaches(mDevice);
+        assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
         if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
             long lastByteOfTheSamePage =
                     offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
-            assertFalse(canReadByte(apkPath, lastByteOfTheSamePage));
-            assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1));
+            assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage + 1));
         }
     }
 
@@ -362,21 +364,22 @@
         long offsetOfLastByte = apkSize - 1;
 
         // The first two pages should be both readable at first.
-        assertTrue(canReadByte(apkPath, offsetOfLastByte));
+        assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
         if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
-            assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+                    offsetOfLastByte - FSVERITY_PAGE_SIZE));
         }
 
         // Damage the file directly against the block device.
         damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
 
         // Expect actual read from disk to fail but only at damaged page.
-        dropCaches();
-        assertFalse(canReadByte(apkPath, offsetOfLastByte));
+        BlockDeviceWriter.dropCaches(mDevice);
+        assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
         if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
             long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
-            assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
-            assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
+            assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage - 1));
         }
     }
 
@@ -395,8 +398,8 @@
                 // from filesystem cache. Forcing GC workarounds the problem.
                 int retry = 5;
                 for (; retry > 0; retry--) {
-                    dropCaches();
-                    if (!canReadByte(path, kTargetOffset)) {
+                    BlockDeviceWriter.dropCaches(mDevice);
+                    if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
                         break;
                     }
                     try {
@@ -451,16 +454,6 @@
         return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
     }
 
-    private void dropCaches() throws DeviceNotAvailableException {
-        expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches");
-    }
-
-    private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException {
-        CommandResult result = mDevice.executeShellV2Command(
-                "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
-        return result.getStatus() == CommandStatus.SUCCESS;
-    }
-
     private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
         CommandResult result = mDevice.executeShellV2Command(cmd);
         assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index d809fe8..43a5078 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -16,8 +16,14 @@
     name: "UpdatableSystemFontTest",
     srcs: ["src/**/*.java"],
     libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
-    static_libs: ["frameworks-base-hostutils"],
+    static_libs: [
+        "block_device_writer_jar",
+        "frameworks-base-hostutils",
+    ],
     test_suites: ["general-tests", "vts"],
+    target_required: [
+        "block_device_writer_module",
+    ],
     data: [
         ":NotoColorEmojiTtf",
         ":UpdatableSystemFontTestCertDer",
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index efe5d70..7b919bd 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -21,6 +21,7 @@
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
+        <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
         <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
         <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
         <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" />
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 6d161a5..e249f8a9 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -21,7 +21,9 @@
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.blockdevicewriter.BlockDeviceWriter;
 import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -34,6 +36,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -126,6 +130,44 @@
                 TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
     }
 
+    @Test
+    public void reboot() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data/fonts/files/");
+
+        expectRemoteCommandToSucceed("stop");
+        expectRemoteCommandToSucceed("start");
+        waitUntilFontCommandIsReady();
+        String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPathAfterReboot).isEqualTo(fontPath);
+    }
+
+    @Test
+    public void reboot_clearDamagedFiles() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data/fonts/files/");
+        assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isTrue();
+
+        BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), fontPath, 0);
+        expectRemoteCommandToSucceed("stop");
+        // We have to make sure system_server is gone before dropping caches, because system_server
+        // process holds font memory maps and prevents cache eviction.
+        waitUntilSystemServerIsGone();
+        BlockDeviceWriter.assertFileNotOpen(getDevice(), fontPath);
+        BlockDeviceWriter.dropCaches(getDevice());
+        assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isFalse();
+
+        expectRemoteCommandToSucceed("start");
+        waitUntilFontCommandIsReady();
+        String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertWithMessage("Damaged file should be deleted")
+                .that(fontPathAfterReboot).startsWith("/system");
+    }
+
     private String getFontPath(String fontFileName) throws Exception {
         // TODO: add a dedicated command for testing.
         String lines = expectRemoteCommandToSucceed("cmd font dump");
@@ -153,4 +195,39 @@
                 .that(result.getStatus())
                 .isNotEqualTo(CommandStatus.SUCCESS);
     }
+
+    private void waitUntilFontCommandIsReady() {
+        waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+            try {
+                return getDevice().executeShellV2Command("cmd font status").getStatus()
+                        == CommandStatus.SUCCESS;
+            } catch (DeviceNotAvailableException e) {
+                return false;
+            }
+        });
+    }
+
+    private void waitUntilSystemServerIsGone() {
+        waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+            try {
+                return getDevice().executeShellV2Command("pid system_server").getStatus()
+                        == CommandStatus.FAILED;
+            } catch (DeviceNotAvailableException e) {
+                return false;
+            }
+        });
+    }
+
+    private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
+        long untilMillis = System.currentTimeMillis() + timeoutMillis;
+        do {
+            if (func.get()) return;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new AssertionError("Interrupted", e);
+            }
+        } while (System.currentTimeMillis() < untilMillis);
+        throw new AssertionError("Timed out");
+    }
 }
diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
index b2bcfeb..ad5bbf2 100644
--- a/tests/net/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
@@ -54,12 +54,26 @@
             }
             .build()
 
+    private val dataFromPasspoint = CaptivePortalData.Builder()
+            .setUserPortalUrl(Uri.parse("https://tc.example.com/passpoint"),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+            .setVenueInfoUrl(Uri.parse("https://venue.example.com/passpoint"),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+            .setCaptive(true)
+            .apply {
+                if (SdkLevel.isAtLeastS()) {
+                    setVenueFriendlyName("venue friendly name")
+                }
+            }
+            .build()
+
     private fun makeBuilder() = CaptivePortalData.Builder(data)
 
     @Test
     fun testParcelUnparcel() {
-        val fieldCount = if (SdkLevel.isAtLeastS()) 8 else 7
+        val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7
         assertParcelSane(data, fieldCount)
+        assertParcelSane(dataFromPasspoint, fieldCount)
 
         assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
         assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
@@ -83,6 +97,27 @@
             assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") }
             assertNotEqualsAfterChange { it.setVenueFriendlyName(null) }
         }
+
+        assertEquals(dataFromPasspoint, CaptivePortalData.Builder(dataFromPasspoint).build())
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint")) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/other"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/passpoint")) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/other"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
     }
 
     @Test
@@ -130,6 +165,22 @@
         assertEquals("venue friendly name", data.venueFriendlyName)
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testGetVenueInfoUrlSource() {
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+                data.venueInfoUrlSource)
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+                dataFromPasspoint.venueInfoUrlSource)
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testGetUserPortalUrlSource() {
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+                data.userPortalUrlSource)
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+                dataFromPasspoint.userPortalUrlSource)
+    }
+
     private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
             CaptivePortalData.Builder(this).apply { mutator(this) }.build()
 
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index fcfb4aa..6a09b02 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -35,6 +35,7 @@
 import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -329,6 +330,9 @@
         mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
         mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
 
+        mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); });
+        mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); });
+
         mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
         mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
         mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
@@ -377,6 +381,13 @@
                 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
+
+        Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        manager.registerSystemDefaultNetworkCallback(callback, handler);
+        verify(mService).requestNetwork(eq(null),
+                eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
+                eq(testPkgName), eq(testAttributionTag));
+        reset(mService);
     }
 
     static Message makeMessage(NetworkRequest req, int messageType) {
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
new file mode 100644
index 0000000..2fd5e38
--- /dev/null
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTransportInfoTest {
+
+    @Test
+    public void testParceling() {
+        VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        assertParcelSane(v, 1 /* fieldCount */);
+        assertParcelingIsLossless(v);
+    }
+
+    @Test
+    public void testEqualsAndHashCode() {
+        VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE);
+        VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        assertNotEquals(v1, v2);
+        assertEquals(v1, v3);
+        assertEquals(v1.hashCode(), v3.hashCode());
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bf61634..e639a36 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -205,6 +205,7 @@
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
+import android.net.VpnTransportInfo;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
@@ -361,8 +362,15 @@
 
     private static final String INTERFACE_NAME = "interface";
 
-    private static final String TEST_VENUE_URL_NA = "https://android.com/";
+    private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/";
+    private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT =
+            "https://android.com/terms/";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER =
+            "https://example.com/terms/";
     private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/";
+    private static final String TEST_USER_PORTAL_API_URL_CAPPORT =
+            "https://android.com/user/api/capport/";
     private static final String TEST_FRIENDLY_NAME = "Network friendly name";
     private static final String TEST_REDIRECT_URL = "http://example.com/firstPath";
 
@@ -1110,7 +1118,7 @@
         }
 
         @Override
-        public int getActiveAppVpnType() {
+        public int getActiveVpnType() {
             return mVpnType;
         }
 
@@ -1123,10 +1131,12 @@
         private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
                 throws Exception {
             if (mAgentRegistered) throw new IllegalStateException("already registered");
+            updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent");
             mConfig = new VpnConfig();
             setUids(uids);
             if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
             mInterface = VPN_IFNAME;
+            mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
             mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
@@ -3286,39 +3296,68 @@
     }
 
     private class CaptivePortalTestData {
-        CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData,
-                CaptivePortalData expectedMergedData) {
-            mNaData = naData;
+        CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData,
+                CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData,
+                CaptivePortalData expectedMergedOtherData) {
+            mNaPasspointData = naPasspointData;
             mCapportData = capportData;
-            mExpectedMergedData = expectedMergedData;
+            mNaOtherData = naOtherData;
+            mExpectedMergedPasspointData = expectedMergedPasspointData;
+            mExpectedMergedOtherData = expectedMergedOtherData;
         }
 
-        public final CaptivePortalData mNaData;
+        public final CaptivePortalData mNaPasspointData;
         public final CaptivePortalData mCapportData;
-        public final CaptivePortalData mExpectedMergedData;
+        public final CaptivePortalData mNaOtherData;
+        public final CaptivePortalData mExpectedMergedPasspointData;
+        public final CaptivePortalData mExpectedMergedOtherData;
+
     }
 
     private CaptivePortalTestData setupCaptivePortalData() {
         final CaptivePortalData capportData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
                 .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+                .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
                 .setExpiryTime(1000000L)
                 .setBytesRemaining(12345L)
                 .build();
 
-        final CaptivePortalData naData = new CaptivePortalData.Builder()
+        final CaptivePortalData naPasspointData = new CaptivePortalData.Builder()
                 .setBytesRemaining(80802L)
-                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
                 .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
 
-        final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder()
+        final CaptivePortalData naOtherData = new CaptivePortalData.Builder()
+                .setBytesRemaining(80802L)
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+                .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+
+        final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
                 .setBytesRemaining(12345L)
                 .setExpiryTime(1000000L)
-                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
                 .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
 
-        return new CaptivePortalTestData(naData, capportData, expectedMergedData);
+        final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder()
+                .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
+                .setBytesRemaining(12345L)
+                .setExpiryTime(1000000L)
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+                .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
+                .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+        return new CaptivePortalTestData(naPasspointData, capportData, naOtherData,
+                expectedMergedPasspointData, expectedMergedOtherData);
     }
 
     @Test
@@ -3332,15 +3371,26 @@
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
                 lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
 
-        // Venue URL and friendly name from Network agent, confirm that API data gets precedence
-        // on the bytes remaining.
+        // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm
+        // that API data gets precedence on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the capport data is merged
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mExpectedMergedPasspointData
+                        .equals(lp.getCaptivePortalData()));
+
+        // Now send this information from non-Passpoint source, confirm that Capport data takes
+        // precedence
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+        // Make sure that the capport data is merged
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mExpectedMergedOtherData
+                        .equals(lp.getCaptivePortalData()));
 
         // Create a new LP with no Network agent capport data
         final LinkProperties newLps = new LinkProperties();
@@ -3357,12 +3407,12 @@
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
                 lp -> lp.getCaptivePortalData() == null);
 
-        newLps.setCaptivePortalData(captivePortalTestData.mNaData);
+        newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(newLps);
 
         // Make sure that only the network agent capport data is available
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
     }
 
     @Test
@@ -3373,12 +3423,12 @@
         // Venue URL and friendly name from Network agent, confirm that API data gets precedence
         // on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the data is saved correctly
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
 
         // Expected merged data: Network agent data is preferred, and values that are not used by
         // it are merged from capport data
@@ -3386,7 +3436,8 @@
 
         // Make sure that the Capport data is merged correctly
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mExpectedMergedPasspointData.equals(
+                        lp.getCaptivePortalData()));
 
         // Now set the naData to null
         linkProperties.setCaptivePortalData(null);
@@ -3397,6 +3448,32 @@
                 lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
     }
 
+    @Test
+    public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport()
+            throws Exception {
+        final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi();
+        final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
+
+        // Venue URL and friendly name from Network agent, confirm that API data gets precedence
+        // on the bytes remaining.
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+        // Make sure that the data is saved correctly
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData()));
+
+        // Expected merged data: Network agent data is preferred, and values that are not used by
+        // it are merged from capport data
+        mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+
+        // Make sure that the Capport data is merged correctly
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mExpectedMergedOtherData.equals(
+                        lp.getCaptivePortalData()));
+    }
+
     private NetworkRequest.Builder newWifiRequestBuilder() {
         return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
     }
@@ -3649,10 +3726,19 @@
 
     @Test
     public void testRegisterDefaultNetworkCallback() throws Exception {
+        // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
         defaultNetworkCallback.assertNoCallback();
 
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler);
+        systemDefaultCallback.assertNoCallback();
+
         // Create a TRANSPORT_CELLULAR request to keep the mobile interface up
         // whenever Wi-Fi is up. Without this, the mobile network agent is
         // reaped before any other activity can take place.
@@ -3667,27 +3753,35 @@
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
         // followed by AVAILABLE cell.
@@ -3695,19 +3789,25 @@
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
         mMockVpn.establishForMyUid();
         assertUidRangesUpdatedForMyUid(true);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(null, systemDefaultCallback.getLastAvailableNetwork());
 
         mMockVpn.disconnect();
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
     }
@@ -6134,6 +6234,10 @@
 
     @Test
     public void testVpnNetworkActive() throws Exception {
+        // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+
         final int uid = Process.myUid();
 
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -6141,6 +6245,7 @@
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
         final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build();
         final NetworkRequest genericRequest = new NetworkRequest.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_VPN).build();
@@ -6154,6 +6259,8 @@
         mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         mCm.registerDefaultNetworkCallback(defaultCallback);
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+                new Handler(ConnectivityThread.getInstanceLooper()));
         defaultCallback.assertNoCallback();
 
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6163,6 +6270,7 @@
         genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -6183,7 +6291,10 @@
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiNetworkAgent.getNetwork(),
+                systemDefaultCallback.getLastAvailableNetwork());
 
         ranges.clear();
         mMockVpn.setUids(ranges);
@@ -6200,6 +6311,7 @@
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
         defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
 
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
@@ -6211,6 +6323,7 @@
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
         defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
 
         mWiFiNetworkAgent.disconnect();
 
@@ -6219,6 +6332,7 @@
         wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
 
         mMockVpn.disconnect();
 
@@ -6227,12 +6341,14 @@
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(null, mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(genericNetworkCallback);
         mCm.unregisterNetworkCallback(wifiNetworkCallback);
         mCm.unregisterNetworkCallback(vpnNetworkCallback);
         mCm.unregisterNetworkCallback(defaultCallback);
+        mCm.unregisterNetworkCallback(systemDefaultCallback);
     }
 
     @Test
@@ -7283,6 +7399,7 @@
     }
 
     private void establishLegacyLockdownVpn() throws Exception {
+        mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
         // The legacy lockdown VPN only supports userId 0.
         final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
         mMockVpn.registerAgent(ranges);
@@ -7395,6 +7512,9 @@
         assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
         assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo();
+        assertNotNull(ti);
+        assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type);
 
         // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
         final LinkProperties wifiLp = new LinkProperties();
@@ -8521,11 +8641,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM);
 
-        try {
-            mService.getConnectionOwnerUid(getTestConnectionInfo());
-            fail("Expected SecurityException for non-VpnService app");
-        } catch (SecurityException expected) {
-        }
+        assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
@@ -8533,11 +8649,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE);
 
-        try {
-            mService.getConnectionOwnerUid(getTestConnectionInfo());
-            fail("Expected SecurityException for non-VpnService app");
-        } catch (SecurityException expected) {
-        }
+        assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index cd4cfcf..46c4081 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -67,6 +67,8 @@
 @SmallTest
 public class NetworkNotificationManagerTest {
 
+    private static final String TEST_SSID = "Test SSID";
+    private static final String TEST_EXTRA_INFO = "extra";
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -76,6 +78,7 @@
 
         WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        WIFI_CAPABILITIES.setSSID(TEST_SSID);
 
         // Set the underyling network to wifi.
         VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
@@ -129,7 +132,7 @@
         when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
         when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
-        when(mNetworkInfo.getExtraInfo()).thenReturn("extra");
+        when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO);
         when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
 
@@ -143,11 +146,11 @@
                 .notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any());
         final int transportType = NetworkNotificationManager.approximateTransportType(nai);
         if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
-            verify(mResources, times(1)).getString(title, eq(any()));
+            verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO));
         } else {
             verify(mResources, times(1)).getString(title);
         }
-        verify(mResources, times(1)).getString(R.string.private_dns_broken_detailed);
+        verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 73cc9f1..cffd2d1d 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -74,6 +75,7 @@
 import android.net.UidRangeParcel;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Build.VERSION_CODES;
@@ -984,6 +986,13 @@
         startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
     }
 
+    private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
+        assertNotNull(ti);
+        assertEquals(type, ti.type);
+    }
+
     public void startRacoon(final String serverAddr, final String expectedAddr)
             throws Exception {
         final ConditionVariable legacyRunnerReady = new ConditionVariable();
@@ -1020,8 +1029,10 @@
 
             // Now wait for the runner to be ready before testing for the route.
             ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+            ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                    ArgumentCaptor.forClass(NetworkCapabilities.class);
             verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
-                    lpCaptor.capture(), any(), anyInt(), any(), anyInt());
+                    lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt());
 
             // In this test the expected address is always v4 so /32.
             // Note that the interface needs to be specified because RouteInfo objects stored in
@@ -1031,6 +1042,8 @@
             final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
             assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
                     actualRoutes.contains(expectedRoute));
+
+            assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
         } finally {
             // Now interrupt the thread, unblock the runner and clean up.
             vpn.mVpnRunner.exitVpnRunner();
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index f9db408..7dada9d 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -65,7 +65,7 @@
                 ArgumentCaptor.forClass(IVcnUnderlyingNetworkPolicyListener.class);
         verify(mMockVcnManagementService).addVcnUnderlyingNetworkPolicyListener(captor.capture());
 
-        assertTrue(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+        assertTrue(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
 
         IVcnUnderlyingNetworkPolicyListener listenerWrapper = captor.getValue();
         listenerWrapper.onPolicyChanged();
@@ -78,7 +78,7 @@
 
         mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+        assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
         verify(mMockVcnManagementService)
                 .addVcnUnderlyingNetworkPolicyListener(
                         any(IVcnUnderlyingNetworkPolicyListener.class));
@@ -88,7 +88,7 @@
     public void testRemoveVcnUnderlyingNetworkPolicyListenerUnknownListener() throws Exception {
         mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+        assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
         verify(mMockVcnManagementService, never())
                 .addVcnUnderlyingNetworkPolicyListener(
                         any(IVcnUnderlyingNetworkPolicyListener.class));
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4859644..c290bff 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
@@ -106,6 +110,7 @@
             Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
 
     private static final int TEST_SUBSCRIPTION_ID = 1;
+    private static final int TEST_SUBSCRIPTION_ID_2 = 2;
     private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
             new SubscriptionInfo(
                     TEST_SUBSCRIPTION_ID /* id */,
@@ -537,59 +542,121 @@
                 Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
     }
 
-    private void verifyMergedNetworkCapabilitiesIsVcnManaged(
-            NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+    private void verifyMergedNetworkCapabilities(
+            NetworkCapabilities mergedCapabilities,
+            @Transport int transportType,
+            boolean isVcnManaged,
+            boolean isRestricted) {
         assertTrue(mergedCapabilities.hasTransport(transportType));
-        assertFalse(
+        assertEquals(
+                !isVcnManaged,
                 mergedCapabilities.hasCapability(
                         NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+        assertEquals(
+                !isRestricted,
+                mergedCapabilities.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+    private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) {
+        setUpVcnSubscription(subId, subGrp);
+        final Vcn vcn = startAndGetVcnInstance(subGrp);
+        doReturn(isVcnActive).when(vcn).isActive();
+    }
+
+    private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport(
+            int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) {
+        setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
+
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+        ncBuilder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        if (transport == TRANSPORT_CELLULAR) {
+            ncBuilder
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
+        } else if (transport == TRANSPORT_WIFI) {
+            WifiInfo wifiInfo = mock(WifiInfo.class);
+            when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+            when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
+
+            ncBuilder
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .setTransportInfo(wifiInfo);
+        } else {
+            throw new IllegalArgumentException("Unknown transport");
+        }
+
+        return mVcnMgmtSvc.getUnderlyingNetworkPolicy(ncBuilder.build(), new LinkProperties());
     }
 
     @Test
     public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
-        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
-
-        NetworkCapabilities nc =
-                new NetworkCapabilities.Builder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
-                        .build();
-
-        VcnUnderlyingNetworkPolicy policy =
-                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR);
 
         assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilitiesIsVcnManaged(
-                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                TRANSPORT_CELLULAR,
+                true /* isVcnManaged */,
+                false /* isRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception {
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID,
+                        TEST_UUID_2,
+                        false /* isActive */,
+                        TRANSPORT_CELLULAR);
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                NetworkCapabilities.TRANSPORT_CELLULAR,
+                false /* isVcnManaged */,
+                false /* isRestricted */);
     }
 
     @Test
     public void testGetUnderlyingNetworkPolicyWifi() throws Exception {
-        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
-
-        WifiInfo wifiInfo = mock(WifiInfo.class);
-        when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
-        when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
-        NetworkCapabilities nc =
-                new NetworkCapabilities.Builder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                        .setTransportInfo(wifiInfo)
-                        .build();
-
-        VcnUnderlyingNetworkPolicy policy =
-                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI);
 
         assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilitiesIsVcnManaged(
-                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                NetworkCapabilities.TRANSPORT_WIFI,
+                true /* isVcnManaged */,
+                true /* isRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception {
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI);
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                NetworkCapabilities.TRANSPORT_WIFI,
+                false /* isVcnManaged */,
+                true /* isRestricted */);
     }
 
     @Test
     public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+        setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */);
+
         NetworkCapabilities nc =
                 new NetworkCapabilities.Builder()
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
                         .build();
 
         VcnUnderlyingNetworkPolicy policy =
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
deleted file mode 100755
index 28ff606..0000000
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Generate API lists for non-SDK API enforcement."""
-import argparse
-from collections import defaultdict, namedtuple
-import functools
-import os
-import re
-import sys
-
-# Names of flags recognized by the `hiddenapi` tool.
-FLAG_SDK = 'sdk'
-FLAG_UNSUPPORTED = 'unsupported'
-FLAG_BLOCKED = 'blocked'
-FLAG_MAX_TARGET_O = 'max-target-o'
-FLAG_MAX_TARGET_P = 'max-target-p'
-FLAG_MAX_TARGET_Q = 'max-target-q'
-FLAG_MAX_TARGET_R = 'max-target-r'
-FLAG_CORE_PLATFORM_API = 'core-platform-api'
-FLAG_PUBLIC_API = 'public-api'
-FLAG_SYSTEM_API = 'system-api'
-FLAG_TEST_API = 'test-api'
-
-# List of all known flags.
-FLAGS_API_LIST = [
-    FLAG_SDK,
-    FLAG_UNSUPPORTED,
-    FLAG_BLOCKED,
-    FLAG_MAX_TARGET_O,
-    FLAG_MAX_TARGET_P,
-    FLAG_MAX_TARGET_Q,
-    FLAG_MAX_TARGET_R,
-]
-ALL_FLAGS = FLAGS_API_LIST + [
-    FLAG_CORE_PLATFORM_API,
-    FLAG_PUBLIC_API,
-    FLAG_SYSTEM_API,
-    FLAG_TEST_API,
-]
-
-FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
-ALL_FLAGS_SET = set(ALL_FLAGS)
-
-# Option specified after one of FLAGS_API_LIST to indicate that
-# only known and otherwise unassigned entries should be assign the
-# given flag.
-# For example, the max-target-P list is checked in as it was in P,
-# but signatures have changes since then. The flag instructs this
-# script to skip any entries which do not exist any more.
-FLAG_IGNORE_CONFLICTS = "ignore-conflicts"
-
-# Option specified after one of FLAGS_API_LIST to express that all
-# apis within a given set of packages should be assign the given flag.
-FLAG_PACKAGES = "packages"
-
-# Option specified after one of FLAGS_API_LIST to indicate an extra
-# tag that should be added to the matching APIs.
-FLAG_TAG = "tag"
-
-# Regex patterns of fields/methods used in serialization. These are
-# considered public API despite being hidden.
-SERIALIZATION_PATTERNS = [
-    r'readObject\(Ljava/io/ObjectInputStream;\)V',
-    r'readObjectNoData\(\)V',
-    r'readResolve\(\)Ljava/lang/Object;',
-    r'serialVersionUID:J',
-    r'serialPersistentFields:\[Ljava/io/ObjectStreamField;',
-    r'writeObject\(Ljava/io/ObjectOutputStream;\)V',
-    r'writeReplace\(\)Ljava/lang/Object;',
-]
-
-# Single regex used to match serialization API. It combines all the
-# SERIALIZATION_PATTERNS into a single regular expression.
-SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$')
-
-# Predicates to be used with filter_apis.
-HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags)
-IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
-
-
-class StoreOrderedOptions(argparse.Action):
-    """An argparse action that stores a number of option arguments in the order that
-    they were specified.
-    """
-    def __call__(self, parser, args, values, option_string = None):
-        items = getattr(args, self.dest, None)
-        if items is None:
-            items = []
-        items.append([option_string.lstrip('-'), values])
-        setattr(args, self.dest, items)
-
-def get_args():
-    """Parses command line arguments.
-
-    Returns:
-        Namespace: dictionary of parsed arguments
-    """
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--output', required=True)
-    parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE',
-        help='CSV files to be merged into output')
-
-    for flag in ALL_FLAGS:
-        parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE',
-            action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"')
-    parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0,
-        action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned '
-        'entries should be assign the given flag. Must follow a list of entries and applies '
-        'to the preceding such list.')
-    parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0,
-        action=StoreOrderedOptions, help='Indicates that the previous list of entries '
-        'is a list of packages. All members in those packages will be given the flag. '
-        'Must follow a list of entries and applies to the preceding such list.')
-    parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1,
-        action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. '
-        'Must follow a list of entries and applies to the preceding such list.')
-
-    return parser.parse_args()
-
-
-def read_lines(filename):
-    """Reads entire file and return it as a list of lines.
-
-    Lines which begin with a hash are ignored.
-
-    Args:
-        filename (string): Path to the file to read from.
-
-    Returns:
-        Lines of the file as a list of string.
-    """
-    with open(filename, 'r') as f:
-        lines = f.readlines();
-    lines = filter(lambda line: not line.startswith('#'), lines)
-    lines = map(lambda line: line.strip(), lines)
-    return set(lines)
-
-
-def write_lines(filename, lines):
-    """Writes list of lines into a file, overwriting the file if it exists.
-
-    Args:
-        filename (string): Path to the file to be writting into.
-        lines (list): List of strings to write into the file.
-    """
-    lines = map(lambda line: line + '\n', lines)
-    with open(filename, 'w') as f:
-        f.writelines(lines)
-
-
-def extract_package(signature):
-    """Extracts the package from a signature.
-
-    Args:
-        signature (string): JNI signature of a method or field.
-
-    Returns:
-        The package name of the class containing the field/method.
-    """
-    full_class_name = signature.split(";->")[0]
-    # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
-    if (full_class_name[0] != "L"):
-        raise ValueError("Expected to start with 'L': %s" % full_class_name)
-    full_class_name = full_class_name[1:]
-    # If full_class_name doesn't contain '/', then package_name will be ''.
-    package_name = full_class_name.rpartition("/")[0]
-    return package_name.replace('/', '.')
-
-
-class FlagsDict:
-    def __init__(self):
-        self._dict_keyset = set()
-        self._dict = defaultdict(set)
-
-    def _check_entries_set(self, keys_subset, source):
-        assert isinstance(keys_subset, set)
-        assert keys_subset.issubset(self._dict_keyset), (
-            "Error: {} specifies signatures not present in code:\n"
-            "{}"
-            "Please visit go/hiddenapi for more information.").format(
-                source, "".join(map(lambda x: "  " + str(x) + "\n", keys_subset - self._dict_keyset)))
-
-    def _check_flags_set(self, flags_subset, source):
-        assert isinstance(flags_subset, set)
-        assert flags_subset.issubset(ALL_FLAGS_SET), (
-            "Error processing: {}\n"
-            "The following flags were not recognized: \n"
-            "{}\n"
-            "Please visit go/hiddenapi for more information.").format(
-                source, "\n".join(flags_subset - ALL_FLAGS_SET))
-
-    def filter_apis(self, filter_fn):
-        """Returns APIs which match a given predicate.
-
-        This is a helper function which allows to filter on both signatures (keys) and
-        flags (values). The built-in filter() invokes the lambda only with dict's keys.
-
-        Args:
-            filter_fn : Function which takes two arguments (signature/flags) and returns a boolean.
-
-        Returns:
-            A set of APIs which match the predicate.
-        """
-        return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset))
-
-    def get_valid_subset_of_unassigned_apis(self, api_subset):
-        """Sanitizes a key set input to only include keys which exist in the dictionary
-        and have not been assigned any API list flags.
-
-        Args:
-            entries_subset (set/list): Key set to be sanitized.
-
-        Returns:
-            Sanitized key set.
-        """
-        assert isinstance(api_subset, set)
-        return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
-
-    def generate_csv(self):
-        """Constructs CSV entries from a dictionary.
-
-        Old versions of flags are used to generate the file.
-
-        Returns:
-            List of lines comprising a CSV file. See "parse_and_merge_csv" for format description.
-        """
-        lines = []
-        for api in self._dict:
-          flags = sorted(self._dict[api])
-          lines.append(",".join([api] + flags))
-        return sorted(lines)
-
-    def parse_and_merge_csv(self, csv_lines, source = "<unknown>"):
-        """Parses CSV entries and merges them into a given dictionary.
-
-        The expected CSV format is:
-            <api signature>,<flag1>,<flag2>,...,<flagN>
-
-        Args:
-            csv_lines (list of strings): Lines read from a CSV file.
-            source (string): Origin of `csv_lines`. Will be printed in error messages.
-
-        Throws:
-            AssertionError if parsed flags are invalid.
-        """
-        # Split CSV lines into arrays of values.
-        csv_values = [ line.split(',') for line in csv_lines ]
-
-        # Update the full set of API signatures.
-        self._dict_keyset.update([ csv[0] for csv in csv_values ])
-
-        # Check that all flags are known.
-        csv_flags = set()
-        for csv in csv_values:
-          csv_flags.update(csv[1:])
-        self._check_flags_set(csv_flags, source)
-
-        # Iterate over all CSV lines, find entry in dict and append flags to it.
-        for csv in csv_values:
-            flags = csv[1:]
-            if (FLAG_PUBLIC_API in flags) or (FLAG_SYSTEM_API in flags):
-                flags.append(FLAG_SDK)
-            self._dict[csv[0]].update(flags)
-
-    def assign_flag(self, flag, apis, source="<unknown>", tag = None):
-        """Assigns a flag to given subset of entries.
-
-        Args:
-            flag (string): One of ALL_FLAGS.
-            apis (set): Subset of APIs to receive the flag.
-            source (string): Origin of `entries_subset`. Will be printed in error messages.
-
-        Throws:
-            AssertionError if parsed API signatures of flags are invalid.
-        """
-        # Check that all APIs exist in the dict.
-        self._check_entries_set(apis, source)
-
-        # Check that the flag is known.
-        self._check_flags_set(set([ flag ]), source)
-
-        # Iterate over the API subset, find each entry in dict and assign the flag to it.
-        for api in apis:
-            self._dict[api].add(flag)
-            if tag:
-                self._dict[api].add(tag)
-
-
-FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
-
-def parse_ordered_flags(ordered_flags):
-    r = []
-    currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None
-    for flag_value in ordered_flags:
-        flag, value = flag_value[0], flag_value[1]
-        if flag in ALL_FLAGS_SET:
-            if currentflag:
-                r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
-                ignore_conflicts, packages, tag = False, False, None
-            currentflag = flag
-            file = value
-        else:
-            if currentflag is None:
-                raise argparse.ArgumentError('--%s is only allowed after one of %s' % (
-                    flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
-            if flag == FLAG_IGNORE_CONFLICTS:
-                ignore_conflicts = True
-            elif flag == FLAG_PACKAGES:
-                packages = True
-            elif flag == FLAG_TAG:
-                tag = value[0]
-
-
-    if currentflag:
-        r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
-    return r
-
-
-def main(argv):
-    # Parse arguments.
-    args = vars(get_args())
-    flagfiles = parse_ordered_flags(args['ordered_flags'])
-
-    # Initialize API->flags dictionary.
-    flags = FlagsDict()
-
-    # Merge input CSV files into the dictionary.
-    # Do this first because CSV files produced by parsing API stubs will
-    # contain the full set of APIs. Subsequent additions from text files
-    # will be able to detect invalid entries, and/or filter all as-yet
-    # unassigned entries.
-    for filename in args["csv"]:
-        flags.parse_and_merge_csv(read_lines(filename), filename)
-
-    # Combine inputs which do not require any particular order.
-    # (1) Assign serialization API to SDK.
-    flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION))
-
-    # (2) Merge text files with a known flag into the dictionary.
-    for info in flagfiles:
-        if (not info.ignore_conflicts) and (not info.packages):
-            flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag)
-
-    # Merge text files where conflicts should be ignored.
-    # This will only assign the given flag if:
-    # (a) the entry exists, and
-    # (b) it has not been assigned any other flag.
-    # Because of (b), this must run after all strict assignments have been performed.
-    for info in flagfiles:
-        if info.ignore_conflicts:
-            valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file))
-            flags.assign_flag(info.flag, valid_entries, filename, info.tag)
-
-    # All members in the specified packages will be assigned the appropriate flag.
-    for info in flagfiles:
-        if info.packages:
-            packages_needing_list = set(read_lines(info.file))
-            should_add_signature_to_list = lambda sig,lists: extract_package(
-                sig) in packages_needing_list and not lists
-            valid_entries = flags.filter_apis(should_add_signature_to_list)
-            flags.assign_flag(info.flag, valid_entries, info.file, info.tag)
-
-    # Mark all remaining entries as blocked.
-    flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))
-
-    # Write output.
-    write_lines(args["output"], flags.generate_csv())
-
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py
deleted file mode 100755
index 82d117f..0000000
--- a/tools/hiddenapi/generate_hiddenapi_lists_test.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the 'License');
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an 'AS IS' BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Unit tests for Hidden API list generation."""
-import unittest
-from generate_hiddenapi_lists import *
-
-class TestHiddenapiListGeneration(unittest.TestCase):
-
-    def test_filter_apis(self):
-        # Initialize flags so that A and B are put on the whitelist and
-        # C, D, E are left unassigned. Try filtering for the unassigned ones.
-        flags = FlagsDict()
-        flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B,' + FLAG_SDK,
-                        'C', 'D', 'E'])
-        filter_set = flags.filter_apis(lambda api, flags: not flags)
-        self.assertTrue(isinstance(filter_set, set))
-        self.assertEqual(filter_set, set([ 'C', 'D', 'E' ]))
-
-    def test_get_valid_subset_of_unassigned_keys(self):
-        # Create flags where only A is unassigned.
-        flags = FlagsDict()
-        flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C'])
-        flags.assign_flag(FLAG_UNSUPPORTED, set(['C']))
-        self.assertEqual(flags.generate_csv(),
-            [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ])
-
-        # Check three things:
-        # (1) B is selected as valid unassigned
-        # (2) A is not selected because it is assigned 'whitelist'
-        # (3) D is not selected because it is not a valid key
-        self.assertEqual(
-            flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ]))
-
-    def test_parse_and_merge_csv(self):
-        flags = FlagsDict()
-
-        # Test empty CSV entry.
-        self.assertEqual(flags.generate_csv(), [])
-
-        # Test new additions.
-        flags.parse_and_merge_csv([
-            'A,' + FLAG_UNSUPPORTED,
-            'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O,
-            'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API,
-            'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
-            'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
-        ])
-        self.assertEqual(flags.generate_csv(), [
-            'A,' + FLAG_UNSUPPORTED,
-            'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O,
-            'C,' + FLAG_SYSTEM_API + ',' + FLAG_SDK,
-            'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
-            'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
-        ])
-
-        # Test unknown flag.
-        with self.assertRaises(AssertionError):
-            flags.parse_and_merge_csv([ 'Z,foo' ])
-
-    def test_assign_flag(self):
-        flags = FlagsDict()
-        flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B'])
-
-        # Test new additions.
-        flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ]))
-        self.assertEqual(flags.generate_csv(),
-            [ 'A,' + FLAG_UNSUPPORTED + "," + FLAG_SDK, 'B,' + FLAG_UNSUPPORTED ])
-
-        # Test invalid API signature.
-        with self.assertRaises(AssertionError):
-            flags.assign_flag(FLAG_SDK, set([ 'C' ]))
-
-        # Test invalid flag.
-        with self.assertRaises(AssertionError):
-            flags.assign_flag('foo', set([ 'A' ]))
-
-    def test_extract_package(self):
-        signature = 'Lcom/foo/bar/Baz;->method1()Lcom/bar/Baz;'
-        expected_package = 'com.foo.bar'
-        self.assertEqual(extract_package(signature), expected_package)
-
-        signature = 'Lcom/foo1/bar/MyClass;->method2()V'
-        expected_package = 'com.foo1.bar'
-        self.assertEqual(extract_package(signature), expected_package)
-
-        signature = 'Lcom/foo_bar/baz/MyClass;->method3()V'
-        expected_package = 'com.foo_bar.baz'
-        self.assertEqual(extract_package(signature), expected_package)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py
deleted file mode 100755
index 6a5b0e1..0000000
--- a/tools/hiddenapi/merge_csv.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Merge multiple CSV files, possibly with different columns.
-"""
-
-import argparse
-import csv
-import io
-
-from zipfile import ZipFile
-
-args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
-args_parser.add_argument('--header', help='Comma separated field names; '
-                                          'if missing determines the header from input files.')
-args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
-args_parser.add_argument('--output', help='Output file for merged CSV.',
-                         default='-', type=argparse.FileType('w'))
-args_parser.add_argument('files', nargs=argparse.REMAINDER)
-args = args_parser.parse_args()
-
-
-def dict_reader(input):
-    return csv.DictReader(input, delimiter=',', quotechar='|')
-
-
-if args.zip_input and len(args.files) > 0:
-    raise ValueError('Expecting either a single ZIP with CSV files'
-                     ' or a list of CSV files as input; not both.')
-
-csv_readers = []
-if len(args.files) > 0:
-    for file in args.files:
-        csv_readers.append(dict_reader(open(file, 'r')))
-elif args.zip_input:
-    with ZipFile(args.zip_input) as zip:
-        for entry in zip.namelist():
-            if entry.endswith('.uau'):
-                csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
-
-headers = set()
-if args.header:
-    fieldnames = args.header.split(',')
-else:
-    # Build union of all columns from source files:
-    for reader in csv_readers:
-        headers = headers.union(reader.fieldnames)
-    fieldnames = sorted(headers)
-
-# Concatenate all files to output:
-writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
-                        dialect='unix', fieldnames=fieldnames)
-writer.writeheader()
-for reader in csv_readers:
-    for row in reader:
-        writer.writerow(row)