Merge "Add listener for SOUNDBAR_MODE DeviceConfig property"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 3bbbb15..e0fffb4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -924,7 +924,8 @@
     @SuppressWarnings("UnsafeParcelApi")
     private JobInfo(Parcel in) {
         jobId = in.readInt();
-        extras = in.readPersistableBundle();
+        final PersistableBundle persistableExtras = in.readPersistableBundle();
+        extras = persistableExtras != null ? persistableExtras : PersistableBundle.EMPTY;
         transientExtras = in.readBundle();
         if (in.readInt() != 0) {
             clipData = ClipData.CREATOR.createFromParcel(in);
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 3764249..33668c7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -337,7 +337,7 @@
      * but there are situations where it may get this wrong and count the JobInfo as changing.
      * (That said, you should be relatively safe with a simple set of consistent data in these
      * fields.)  You should never use {@link JobInfo.Builder#setClipData(ClipData, int)} with
-     * work you are enqueue, since currently this will always be treated as a different JobInfo,
+     * work you are enqueuing, since currently this will always be treated as a different JobInfo,
      * even if the ClipData contents are exactly the same.</p>
      *
      * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
@@ -345,6 +345,16 @@
      * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
      * this API if calls are made too frequently in a short amount of time.
      *
+     * <p class="caution"><strong>Note:</strong> Prior to Android version
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
+     * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
+     * an {@link IllegalArgumentException} if they attempted to do so. Starting with
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * JobWorkItems can be persisted alongside the hosting job.
+     * However, Intents cannot be persisted. Set a {@link PersistableBundle} using
+     * {@link JobWorkItem.Builder#setExtras(PersistableBundle)} for any information that needs
+     * to be persisted.
+     *
      * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
      * job.
      *
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
index 32945e0..18167e2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
@@ -19,20 +19,33 @@
 import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
 
 import android.annotation.BytesLong;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.compat.Compatibility;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 
 /**
  * A unit of work that can be enqueued for a job using
  * {@link JobScheduler#enqueue JobScheduler.enqueue}.  See
  * {@link JobParameters#dequeueWork() JobParameters.dequeueWork} for more details.
+ *
+ * <p class="caution"><strong>Note:</strong> Prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
+ * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
+ * an {@link IllegalArgumentException} if they attempted to do so. Starting with
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems can be persisted alongside
+ * the hosting job. However, Intents cannot be persisted. Set a {@link PersistableBundle} using
+ * {@link Builder#setExtras(PersistableBundle)} for any information that needs to be persisted.
  */
 final public class JobWorkItem implements Parcelable {
+    @NonNull
+    private final PersistableBundle mExtras;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     final Intent mIntent;
     private final long mNetworkDownloadBytes;
@@ -49,6 +62,10 @@
      * Create a new piece of work, which can be submitted to
      * {@link JobScheduler#enqueue JobScheduler.enqueue}.
      *
+     * <p>
+     * Intents cannot be used for persisted JobWorkItems.
+     * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+     *
      * @param intent The general Intent describing this work.
      */
     public JobWorkItem(Intent intent) {
@@ -62,6 +79,10 @@
      * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
      * details about how to estimate network traffic.
      *
+     * <p>
+     * Intents cannot be used for persisted JobWorkItems.
+     * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+     *
      * @param intent The general Intent describing this work.
      * @param downloadBytes The estimated size of network traffic that will be
      *            downloaded by this job work item, in bytes.
@@ -79,6 +100,10 @@
      * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
      * details about how to estimate network traffic.
      *
+     * <p>
+     * Intents cannot be used for persisted JobWorkItems.
+     * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+     *
      * @param intent            The general Intent describing this work.
      * @param downloadBytes     The estimated size of network traffic that will be
      *                          downloaded by this job work item, in bytes.
@@ -89,6 +114,7 @@
      */
     public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes,
             @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) {
+        mExtras = PersistableBundle.EMPTY;
         mIntent = intent;
         mNetworkDownloadBytes = downloadBytes;
         mNetworkUploadBytes = uploadBytes;
@@ -96,6 +122,25 @@
         enforceValidity(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
     }
 
+    private JobWorkItem(@NonNull Builder builder) {
+        mDeliveryCount = builder.mDeliveryCount;
+        mExtras = builder.mExtras.deepCopy();
+        mIntent = builder.mIntent;
+        mNetworkDownloadBytes = builder.mNetworkDownloadBytes;
+        mNetworkUploadBytes = builder.mNetworkUploadBytes;
+        mMinimumChunkBytes = builder.mMinimumNetworkChunkBytes;
+    }
+
+    /**
+     * Return the extras associated with this work.
+     *
+     * @see Builder#setExtras(PersistableBundle)
+     */
+    @NonNull
+    public PersistableBundle getExtras() {
+        return mExtras;
+    }
+
     /**
      * Return the Intent associated with this work.
      */
@@ -176,6 +221,7 @@
     /**
      * @hide
      */
+    @Nullable
     public Object getGrants() {
         return mGrants;
     }
@@ -186,6 +232,8 @@
         sb.append(mWorkId);
         sb.append(" intent=");
         sb.append(mIntent);
+        sb.append(" extras=");
+        sb.append(mExtras);
         if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) {
             sb.append(" downloadBytes=");
             sb.append(mNetworkDownloadBytes);
@@ -207,6 +255,140 @@
     }
 
     /**
+     * Builder class for constructing {@link JobWorkItem} objects.
+     */
+    public static final class Builder {
+        private int mDeliveryCount;
+        private PersistableBundle mExtras = PersistableBundle.EMPTY;
+        private Intent mIntent;
+        private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
+        private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
+        private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN;
+
+        /**
+         * Initialize a new Builder to construct a {@link JobWorkItem} object.
+         */
+        public Builder() {
+        }
+
+        /**
+         * @see JobWorkItem#getDeliveryCount()
+         * @return This object for method chaining
+         * @hide
+         */
+        @NonNull
+        public Builder setDeliveryCount(int deliveryCount) {
+            mDeliveryCount = deliveryCount;
+            return this;
+        }
+
+        /**
+         * Set optional extras. This can be persisted, so we only allow primitive types.
+         * @param extras Bundle containing extras you want the scheduler to hold on to for you.
+         * @return This object for method chaining
+         * @see JobWorkItem#getExtras()
+         */
+        @NonNull
+        public Builder setExtras(@NonNull PersistableBundle extras) {
+            if (extras == null) {
+                throw new IllegalArgumentException("extras cannot be null");
+            }
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Set an intent with information relevant to this work item.
+         *
+         * <p>
+         * Intents cannot be used for persisted JobWorkItems.
+         * Use {@link #setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+         *
+         * @return This object for method chaining
+         * @see JobWorkItem#getIntent()
+         */
+        @NonNull
+        public Builder setIntent(@NonNull Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Set the estimated size of network traffic that will be performed for this work item,
+         * in bytes.
+         *
+         * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
+         * details about how to estimate network traffic.
+         *
+         * @param downloadBytes The estimated size of network traffic that will be
+         *                      downloaded for this work item, in bytes.
+         * @param uploadBytes   The estimated size of network traffic that will be
+         *                      uploaded for this work item, in bytes.
+         * @return This object for method chaining
+         * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+         * @see JobWorkItem#getEstimatedNetworkDownloadBytes()
+         * @see JobWorkItem#getEstimatedNetworkUploadBytes()
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
+                @BytesLong long uploadBytes) {
+            if (downloadBytes != NETWORK_BYTES_UNKNOWN && downloadBytes < 0) {
+                throw new IllegalArgumentException(
+                        "Invalid network download bytes: " + downloadBytes);
+            }
+            if (uploadBytes != NETWORK_BYTES_UNKNOWN && uploadBytes < 0) {
+                throw new IllegalArgumentException("Invalid network upload bytes: " + uploadBytes);
+            }
+            mNetworkDownloadBytes = downloadBytes;
+            mNetworkUploadBytes = uploadBytes;
+            return this;
+        }
+
+        /**
+         * Set the minimum size of non-resumable network traffic this work item requires, in bytes.
+         * When the upload or download can be easily paused and resumed, use this to set the
+         * smallest size that must be transmitted between start and stop events to be considered
+         * successful. If the transfer cannot be paused and resumed, then this should be the sum
+         * of the values provided to {@link #setEstimatedNetworkBytes(long, long)}.
+         *
+         * See {@link JobInfo.Builder#setMinimumNetworkChunkBytes(long)} for
+         * details about how to set the minimum chunk.
+         *
+         * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and
+         *                       resumed, in bytes.
+         * @return This object for method chaining
+         * @see JobInfo.Builder#setMinimumNetworkChunkBytes(long)
+         * @see JobWorkItem#getMinimumNetworkChunkBytes()
+         * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long)
+         */
+        @NonNull
+        public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) {
+            if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) {
+                throw new IllegalArgumentException("Minimum chunk size must be positive");
+            }
+            mMinimumNetworkChunkBytes = chunkSizeBytes;
+            return this;
+        }
+
+        /**
+         * @return The JobWorkItem object to hand to the JobScheduler. This object is immutable.
+         */
+        @NonNull
+        public JobWorkItem build() {
+            return build(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
+        }
+
+        /** @hide */
+        @NonNull
+        public JobWorkItem build(boolean rejectNegativeNetworkEstimates) {
+            JobWorkItem jobWorkItem = new JobWorkItem(this);
+            jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
+            return jobWorkItem;
+        }
+    }
+
+    /**
      * @hide
      */
     public void enforceValidity(boolean rejectNegativeNetworkEstimates) {
@@ -249,6 +431,7 @@
         } else {
             out.writeInt(0);
         }
+        out.writePersistableBundle(mExtras);
         out.writeLong(mNetworkDownloadBytes);
         out.writeLong(mNetworkUploadBytes);
         out.writeLong(mMinimumChunkBytes);
@@ -274,6 +457,8 @@
         } else {
             mIntent = null;
         }
+        final PersistableBundle extras = in.readPersistableBundle();
+        mExtras = extras != null ? extras : PersistableBundle.EMPTY;
         mNetworkDownloadBytes = in.readLong();
         mNetworkUploadBytes = in.readLong();
         mMinimumChunkBytes = in.readLong();
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 c7a2997..8defa16 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1406,6 +1406,7 @@
                 if (toCancel.getJob().equals(job)) {
 
                     toCancel.enqueueWorkLocked(work);
+                    mJobs.touchJob(toCancel);
 
                     // If any of work item is enqueued when the source is in the foreground,
                     // exempt the entire job.
@@ -3775,6 +3776,14 @@
                         }
                     }
                 }
+                if (job.isPersisted()) {
+                    // Intent.saveToXml() doesn't persist everything, so just reject all
+                    // JobWorkItems with Intents to be safe/predictable.
+                    if (jobWorkItem.getIntent() != null) {
+                        throw new IllegalArgumentException(
+                                "Cannot persist JobWorkItems with Intents");
+                    }
+                }
             }
             return JobScheduler.RESULT_SUCCESS;
         }
@@ -3837,9 +3846,6 @@
             final int userId = UserHandle.getUserId(uid);
 
             enforceValidJobRequest(uid, job);
-            if (job.isPersisted()) {
-                throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
-            }
             if (work == null) {
                 throw new NullPointerException("work is null");
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 285b982..ce7da86 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -615,6 +615,9 @@
                             "last work dequeued");
                     // This will finish the job.
                     doCallbackLocked(false, "last work dequeued");
+                } else {
+                    // Delivery count has been updated, so persist JobWorkItem change.
+                    mService.mJobs.touchJob(mRunningJob);
                 }
                 return work;
             }
@@ -632,6 +635,7 @@
                     // Exception-throwing-can down the road to JobParameters.completeWork >:(
                     return true;
                 }
+                mService.mJobs.touchJob(mRunningJob);
                 return mRunningJob.completeWorkLocked(workId);
             }
         } finally {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 5f5f447..88270494 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.net.NetworkRequest;
@@ -310,6 +311,15 @@
         mJobSet.removeJobsOfUnlistedUsers(keepUserIds);
     }
 
+    /** Note a change in the specified JobStatus that necessitates writing job state to disk. */
+    void touchJob(@NonNull JobStatus jobStatus) {
+        if (!jobStatus.isPersisted()) {
+            return;
+        }
+        mPendingJobWriteUids.put(jobStatus.getUid(), true);
+        maybeWriteStatusToDiskAsync();
+    }
+
     @VisibleForTesting
     public void clear() {
         mJobSet.clear();
@@ -430,6 +440,7 @@
     private static final String XML_TAG_PERIODIC = "periodic";
     private static final String XML_TAG_ONEOFF = "one-off";
     private static final String XML_TAG_EXTRAS = "extras";
+    private static final String XML_TAG_JOB_WORK_ITEM = "job-work-item";
 
     private void migrateJobFilesAsync() {
         synchronized (mLock) {
@@ -724,6 +735,7 @@
                     writeConstraintsToXml(out, jobStatus);
                     writeExecutionCriteriaToXml(out, jobStatus);
                     writeBundleToXml(jobStatus.getJob().getExtras(), out);
+                    writeJobWorkItemsToXml(out, jobStatus);
                     out.endTag(null, XML_TAG_JOB);
 
                     numJobs++;
@@ -906,6 +918,53 @@
                 out.endTag(null, XML_TAG_ONEOFF);
             }
         }
+
+        private void writeJobWorkItemsToXml(@NonNull TypedXmlSerializer out,
+                @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException {
+            // Write executing first since they're technically at the front of the queue.
+            writeJobWorkItemListToXml(out, jobStatus.executingWork);
+            writeJobWorkItemListToXml(out, jobStatus.pendingWork);
+        }
+
+        private void writeJobWorkItemListToXml(@NonNull TypedXmlSerializer out,
+                @Nullable List<JobWorkItem> jobWorkItems)
+                throws IOException, XmlPullParserException {
+            if (jobWorkItems == null) {
+                return;
+            }
+            // Write the items in list order to maintain the enqueue order.
+            final int size = jobWorkItems.size();
+            for (int i = 0; i < size; ++i) {
+                final JobWorkItem item = jobWorkItems.get(i);
+                if (item.getGrants() != null) {
+                    // We currently don't allow persisting jobs when grants are involved.
+                    // TODO(256618122): allow persisting JobWorkItems with grant flags
+                    continue;
+                }
+                if (item.getIntent() != null) {
+                    // Intent.saveToXml() doesn't persist everything, so we shouldn't attempt to
+                    // persist these JobWorkItems at all.
+                    Slog.wtf(TAG, "Encountered JobWorkItem with Intent in persisting list");
+                    continue;
+                }
+                out.startTag(null, XML_TAG_JOB_WORK_ITEM);
+                out.attributeInt(null, "delivery-count", item.getDeliveryCount());
+                if (item.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "estimated-download-bytes",
+                            item.getEstimatedNetworkDownloadBytes());
+                }
+                if (item.getEstimatedNetworkUploadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "estimated-upload-bytes",
+                            item.getEstimatedNetworkUploadBytes());
+                }
+                if (item.getMinimumNetworkChunkBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "minimum-network-chunk-bytes",
+                            item.getMinimumNetworkChunkBytes());
+                }
+                writeBundleToXml(item.getExtras(), out);
+                out.endTag(null, XML_TAG_JOB_WORK_ITEM);
+            }
+        }
     };
 
     /**
@@ -1262,7 +1321,13 @@
                 Slog.e(TAG, "Persisted extras contained invalid data", e);
                 return null;
             }
-            parser.nextTag(); // Consume </extras>
+            eventType = parser.nextTag(); // Consume </extras>
+
+            List<JobWorkItem> jobWorkItems = null;
+            if (eventType == XmlPullParser.START_TAG
+                    && XML_TAG_JOB_WORK_ITEM.equals(parser.getName())) {
+                jobWorkItems = readJobWorkItemsFromXml(parser);
+            }
 
             final JobInfo builtJob;
             try {
@@ -1301,6 +1366,11 @@
                     elapsedRuntimes.first, elapsedRuntimes.second,
                     lastSuccessfulRunTime, lastFailedRunTime,
                     (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
+            if (jobWorkItems != null) {
+                for (int i = 0; i < jobWorkItems.size(); ++i) {
+                    js.enqueueWorkLocked(jobWorkItems.get(i));
+                }
+            }
             return js;
         }
 
@@ -1473,6 +1543,64 @@
                     parser.getAttributeLong(null, "deadline", JobStatus.NO_LATEST_RUNTIME);
             return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
         }
+
+        @NonNull
+        private List<JobWorkItem> readJobWorkItemsFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            List<JobWorkItem> jobWorkItems = new ArrayList<>();
+
+            for (int eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT;
+                    eventType = parser.next()) {
+                final String tagName = parser.getName();
+                if (!XML_TAG_JOB_WORK_ITEM.equals(tagName)) {
+                    // We're no longer operating with work items.
+                    break;
+                }
+                try {
+                    JobWorkItem jwi = readJobWorkItemFromXml(parser);
+                    if (jwi != null) {
+                        jobWorkItems.add(jwi);
+                    }
+                } catch (Exception e) {
+                    // If there's an issue with one JobWorkItem, drop only the one item and not the
+                    // whole job.
+                    Slog.e(TAG, "Problem with persisted JobWorkItem", e);
+                }
+            }
+
+            return jobWorkItems;
+        }
+
+        @Nullable
+        private JobWorkItem readJobWorkItemFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            JobWorkItem.Builder jwiBuilder = new JobWorkItem.Builder();
+
+            jwiBuilder
+                    .setDeliveryCount(parser.getAttributeInt(null, "delivery-count"))
+                    .setEstimatedNetworkBytes(
+                            parser.getAttributeLong(null,
+                                    "estimated-download-bytes", JobInfo.NETWORK_BYTES_UNKNOWN),
+                            parser.getAttributeLong(null,
+                                    "estimated-upload-bytes", JobInfo.NETWORK_BYTES_UNKNOWN))
+                    .setMinimumNetworkChunkBytes(parser.getAttributeLong(null,
+                            "minimum-network-chunk-bytes", JobInfo.NETWORK_BYTES_UNKNOWN));
+            parser.next();
+            try {
+                final PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
+                jwiBuilder.setExtras(extras);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Persisted extras contained invalid data", e);
+                return null;
+            }
+
+            try {
+                return jwiBuilder.build();
+            } catch (Exception e) {
+                Slog.e(TAG, "Invalid JobWorkItem", e);
+                return null;
+            }
+        }
     }
 
     /** Set of all tracked jobs. */
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 5712599..b0b6a01 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -676,6 +676,12 @@
                 Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
             }
         }
+        if (jobStatus.executingWork != null && jobStatus.executingWork.size() > 0) {
+            executingWork = new ArrayList<>(jobStatus.executingWork);
+        }
+        if (jobStatus.pendingWork != null && jobStatus.pendingWork.size() > 0) {
+            pendingWork = new ArrayList<>(jobStatus.pendingWork);
+        }
     }
 
     /**
diff --git a/core/api/current.txt b/core/api/current.txt
index 5b2b37f..b63b0aa 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -935,6 +935,8 @@
     field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
     field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
     field public static final int keyboardLayout = 16843691; // 0x10103ab
+    field public static final int keyboardLayoutType;
+    field public static final int keyboardLocale;
     field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
@@ -5650,6 +5652,11 @@
     field public static final int MODE_UNKNOWN = 0; // 0x0
   }
 
+  public class GrammaticalInflectionManager {
+    method public int getApplicationGrammaticalGender();
+    method public void setRequestedApplicationGrammaticalGender(int);
+  }
+
   public class Instrumentation {
     ctor public Instrumentation();
     method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -8588,12 +8595,22 @@
     method public int getDeliveryCount();
     method public long getEstimatedNetworkDownloadBytes();
     method public long getEstimatedNetworkUploadBytes();
+    method @NonNull public android.os.PersistableBundle getExtras();
     method public android.content.Intent getIntent();
     method public long getMinimumNetworkChunkBytes();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
   }
 
+  public static final class JobWorkItem.Builder {
+    ctor public JobWorkItem.Builder();
+    method @NonNull public android.app.job.JobWorkItem build();
+    method @NonNull public android.app.job.JobWorkItem.Builder setEstimatedNetworkBytes(long, long);
+    method @NonNull public android.app.job.JobWorkItem.Builder setExtras(@NonNull android.os.PersistableBundle);
+    method @NonNull public android.app.job.JobWorkItem.Builder setIntent(@NonNull android.content.Intent);
+    method @NonNull public android.app.job.JobWorkItem.Builder setMinimumNetworkChunkBytes(long);
+  }
+
 }
 
 package android.app.people {
@@ -10005,6 +10022,7 @@
     field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
     field public static final String FINGERPRINT_SERVICE = "fingerprint";
     field public static final String GAME_SERVICE = "game";
+    field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final String HEALTHCONNECT_SERVICE = "healthconnect";
     field public static final String INPUT_METHOD_SERVICE = "input_method";
@@ -11261,6 +11279,7 @@
     field public static final int CONFIG_DENSITY = 4096; // 0x1000
     field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
     field public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 268435456; // 0x10000000
+    field public static final int CONFIG_GRAMMATICAL_GENDER = 32768; // 0x8000
     field public static final int CONFIG_KEYBOARD = 16; // 0x10
     field public static final int CONFIG_KEYBOARD_HIDDEN = 32; // 0x20
     field public static final int CONFIG_LAYOUT_DIRECTION = 8192; // 0x2000
@@ -11809,6 +11828,7 @@
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
+    method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, int, @NonNull android.content.IntentSender);
     method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public void uninstallExistingPackage(@NonNull String, @Nullable android.content.IntentSender);
     method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap);
@@ -11937,6 +11957,8 @@
     method public int getInstallReason();
     method @Nullable public String getInstallerAttributionTag();
     method @Nullable public String getInstallerPackageName();
+    method public int getInstallerUid();
+    method @NonNull public boolean getIsPreApprovalRequested();
     method public int getMode();
     method public int getOriginatingUid();
     method @Nullable public android.net.Uri getOriginatingUri();
@@ -11987,6 +12009,7 @@
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setInstallScenario(int);
+    method public void setInstallerPackageName(@Nullable String);
     method public void setKeepApplicationEnabledSetting();
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
@@ -12247,6 +12270,7 @@
     field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = "android.hardware.identity_credential_direct_access";
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+    field public static final String FEATURE_IPSEC_TUNNEL_MIGRATION = "android.software.ipsec_tunnel_migration";
     field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
     field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
     field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
@@ -12875,6 +12899,7 @@
     method public int diff(android.content.res.Configuration);
     method public boolean equals(android.content.res.Configuration);
     method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
+    method public int getGrammaticalGender();
     method public int getLayoutDirection();
     method @NonNull public android.os.LocaleList getLocales();
     method public boolean isLayoutSizeAtLeast(int);
@@ -12904,6 +12929,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
     field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
     field public static final int FONT_WEIGHT_ADJUSTMENT_UNDEFINED = 2147483647; // 0x7fffffff
+    field public static final int GRAMMATICAL_GENDER_FEMININE = 3; // 0x3
+    field public static final int GRAMMATICAL_GENDER_MASCULINE = 4; // 0x4
+    field public static final int GRAMMATICAL_GENDER_NEUTRAL = 2; // 0x2
+    field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
     field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_YES = 2; // 0x2
@@ -14926,6 +14955,8 @@
     enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
     enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
     enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020_HLG;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020_PQ;
     enum_constant public static final android.graphics.ColorSpace.Named BT709;
     enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
     enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
@@ -19962,8 +19993,9 @@
     method public int describeContents();
     method @NonNull public android.location.GnssClock getClock();
     method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
-    method public boolean getIsFullTracking();
     method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
+    method public boolean hasFullTracking();
+    method public boolean isFullTracking();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
   }
@@ -19972,9 +20004,10 @@
     ctor public GnssMeasurementsEvent.Builder();
     ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent);
     method @NonNull public android.location.GnssMeasurementsEvent build();
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder clearFullTracking();
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
-    method @NonNull public android.location.GnssMeasurementsEvent.Builder setIsFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
   }
 
@@ -24191,6 +24224,7 @@
     method public int getDisableReason();
     method public int getFlags();
     method @NonNull public String getRouteId();
+    method public int getSessionParticipantCount();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
     field public static final int DISABLE_REASON_AD = 3; // 0x3
@@ -24206,6 +24240,7 @@
     method @NonNull public android.media.RouteListingPreference.Item build();
     method @NonNull public android.media.RouteListingPreference.Item.Builder setDisableReason(int);
     method @NonNull public android.media.RouteListingPreference.Item.Builder setFlags(int);
+    method @NonNull public android.media.RouteListingPreference.Item.Builder setSessionParticipantCount(@IntRange(from=0) int);
   }
 
   public final class RoutingSessionInfo implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bd64f4d..ef074d8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1496,11 +1496,13 @@
     method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getDataManagementIntent(String);
     method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public CharSequence getDataManagementIntentLabel(@NonNull String);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public String getDataManagementLabel(@NonNull String);
+    method @NonNull public android.app.backup.BackupRestoreEventLogger getDelayedRestoreLogger();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public String getDestinationString(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isAppEligibleForBackup(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupEnabled();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupServiceActive(android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public String[] listAllTransports();
+    method @NonNull public void reportDelayedRestoreResult(@NonNull android.app.backup.BackupRestoreEventLogger);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int);
     method @Deprecated public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
@@ -3331,6 +3333,7 @@
     field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
     field public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE";
     field public static final String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET";
+    field public static final String EXTRA_INSTALL_RESULT = "android.intent.extra.INSTALL_RESULT";
     field public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
     field public static final String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES";
     field public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS";
@@ -3346,6 +3349,7 @@
     field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
     field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
+    field public static final String EXTRA_UNINSTALL_ALL_USERS = "android.intent.extra.UNINSTALL_ALL_USERS";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
@@ -3447,9 +3451,11 @@
 package android.content.pm {
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public boolean hasFragileUserData();
     method public boolean isEncryptionAware();
     method public boolean isInstantApp();
     method public boolean isOem();
+    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public boolean isPrivilegedApp();
     method public boolean isProduct();
     method public boolean isVendor();
     field public String credentialProtectedDataDir;
@@ -3567,16 +3573,32 @@
   }
 
   public class PackageInstaller {
+    method @NonNull public android.content.pm.PackageInstaller.InstallInfo getInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
+    field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
+    field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
     field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
     field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0
     field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1
+    field public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
     field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+    field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
+    field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
   }
 
+  public static class PackageInstaller.InstallInfo {
+    method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
+    method public int getInstallLocation();
+    method @NonNull public String getPackageName();
+  }
+
+  public static class PackageInstaller.PackageParsingException extends java.lang.Exception {
+    method public int getErrorCode();
+  }
+
   public static class PackageInstaller.Session implements java.io.Closeable {
     method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
@@ -3623,6 +3645,7 @@
   public abstract class PackageManager {
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract boolean arePermissionsIndividuallyControlled();
+    method @NonNull public boolean canUserUninstall(@NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
     method @NonNull @RequiresPermission("android.permission.GET_APP_METADATA") public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -3641,6 +3664,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -3668,11 +3692,19 @@
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
+    method @NonNull public boolean shouldShowNewAppInstalledNotification();
     method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+    field public static final int DELETE_ALL_USERS = 2; // 0x2
+    field public static final int DELETE_FAILED_ABORTED = -5; // 0xfffffffb
+    field public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; // 0xfffffffe
+    field public static final int DELETE_FAILED_INTERNAL_ERROR = -1; // 0xffffffff
+    field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
+    field public static final int DELETE_KEEP_DATA = 1; // 0x1
+    field public static final int DELETE_SUCCEEDED = 1; // 0x1
     field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -3780,6 +3812,11 @@
   @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
+  public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+    method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
+  }
+
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     field @StringRes public final int backgroundRequestDetailResourceId;
     field @StringRes public final int backgroundRequestResourceId;
@@ -4865,17 +4902,15 @@
 
   public final class VirtualTouchscreenConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
     method public int describeContents();
-    method public int getHeightInPixels();
-    method public int getWidthInPixels();
+    method public int getHeight();
+    method public int getWidth();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchscreenConfig> CREATOR;
   }
 
   public static final class VirtualTouchscreenConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualTouchscreenConfig.Builder> {
-    ctor public VirtualTouchscreenConfig.Builder();
+    ctor public VirtualTouchscreenConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
     method @NonNull public android.hardware.input.VirtualTouchscreenConfig build();
-    method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setHeightInPixels(int);
-    method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setWidthInPixels(int);
   }
 
 }
@@ -6569,6 +6604,7 @@
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnNonDefaultDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener) throws java.lang.SecurityException;
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
@@ -6590,6 +6626,7 @@
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getMutingExpectedDevice();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getNonDefaultDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -6605,6 +6642,8 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removeDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnNonDefaultDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
@@ -6616,6 +6655,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes);
@@ -6660,6 +6700,10 @@
     field public static final int EVENT_TIMEOUT = 2; // 0x2
   }
 
+  public static interface AudioManager.OnNonDefaultDevicesForStrategyChangedListener {
+    method public void onNonDefaultDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
+  }
+
   @Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
     method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
   }
@@ -7744,6 +7788,7 @@
     field public static final int STATUS_DATA_READY = 1; // 0x1
     field public static final int STATUS_HIGH_WATER = 4; // 0x4
     field public static final int STATUS_LOW_WATER = 2; // 0x2
+    field public static final int STATUS_NO_DATA = 16; // 0x10
     field public static final int STATUS_OVERFLOW = 8; // 0x8
     field public static final int SUBTYPE_AUDIO = 3; // 0x3
     field public static final int SUBTYPE_DOWNLOAD = 5; // 0x5
@@ -10923,6 +10968,7 @@
     field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
     field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
+    field public static final String ACTION_USER_SETTINGS = "android.settings.USER_SETTINGS";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c1cbeef..233dee9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -814,7 +814,7 @@
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     method public boolean hasRequestForegroundServiceExemption();
     method public boolean isOnBackInvokedCallbackEnabled();
-    method public boolean isPrivilegedApp();
+    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public boolean isPrivilegedApp();
     method public boolean isSystemApp();
     method public void setEnableOnBackInvokedCallback(boolean);
     field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
@@ -831,7 +831,6 @@
 
   public static class PackageInstaller.SessionParams implements android.os.Parcelable {
     method public void setInstallFlagAllowTest();
-    method public void setInstallerPackageName(@Nullable String);
   }
 
   public abstract class PackageManager {
@@ -1299,6 +1298,7 @@
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
     method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
     method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+    method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
     method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9ce196d..8e747b2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -62,6 +62,7 @@
 import android.app.servertransaction.TransactionExecutor;
 import android.app.servertransaction.TransactionExecutorHelper;
 import android.bluetooth.BluetoothFrameworkInitializer;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
@@ -361,6 +362,8 @@
     private int mLastProcessState = PROCESS_STATE_UNKNOWN;
     ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
     private int mLastSessionId;
+    // Holds the value of the last reported device ID value from the server for the top activity.
+    int mLastReportedDeviceId;
     final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
     @UnsupportedAppUsage
     final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -546,6 +549,9 @@
         boolean hideForNow;
         Configuration createdConfig;
         Configuration overrideConfig;
+        // TODO(b/263402465): pass deviceId directly in LaunchActivityItem#execute
+        // The deviceId assigned by the server when this activity was first started.
+        int mDeviceId;
         // Used for consolidating configs before sending on to Activity.
         private Configuration tmpConfig = new Configuration();
         // Callback used for updating activity override config and camera compat control state.
@@ -608,7 +614,7 @@
         }
 
         public ActivityClientRecord(IBinder token, Intent intent, int ident,
-                ActivityInfo info, Configuration overrideConfig,
+                ActivityInfo info, Configuration overrideConfig, int deviceId,
                 String referrer, IVoiceInteractor voiceInteractor, Bundle state,
                 PersistableBundle persistentState, List<ResultInfo> pendingResults,
                 List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -630,6 +636,7 @@
             this.isForward = isForward;
             this.profilerInfo = profilerInfo;
             this.overrideConfig = overrideConfig;
+            this.mDeviceId = deviceId;
             this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
             mActivityOptions = activityOptions;
             mLaunchedFromBubble = launchedFromBubble;
@@ -3816,6 +3823,7 @@
 
         // Make sure we are running with the most recent config.
         mConfigurationController.handleConfigurationChanged(null, null);
+        updateDeviceIdForNonUIContexts(r.mDeviceId);
 
         if (localLOGV) Slog.v(
             TAG, "Handling launch of " + r);
@@ -4548,6 +4556,9 @@
             context.setOuterContext(service);
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
+            if (!service.isUiContext()) { // WindowProviderService is a UI Context.
+                service.updateDeviceId(mLastReportedDeviceId);
+            }
             service.onCreate();
             mServicesData.put(data.token, data);
             mServices.put(data.token, service);
@@ -6066,9 +6077,48 @@
         }
     }
 
+    private void updateDeviceIdForNonUIContexts(int deviceId) {
+        // Invalid device id is treated as a no-op.
+        if (deviceId == VirtualDeviceManager.DEVICE_ID_INVALID) {
+            return;
+        }
+        if (deviceId == mLastReportedDeviceId) {
+            return;
+        }
+        mLastReportedDeviceId = deviceId;
+        ArrayList<Context> nonUIContexts = new ArrayList<>();
+        // Update Application and Service contexts with implicit device association.
+        // UI Contexts are able to derived their device Id association from the display.
+        synchronized (mResourcesManager) {
+            final int numApps = mAllApplications.size();
+            for (int i = 0; i < numApps; i++) {
+                nonUIContexts.add(mAllApplications.get(i));
+            }
+            final int numServices = mServices.size();
+            for (int i = 0; i < numServices; i++) {
+                final Service service = mServices.valueAt(i);
+                // WindowProviderService is a UI Context.
+                if (!service.isUiContext()) {
+                    nonUIContexts.add(service);
+                }
+            }
+        }
+        for (Context context : nonUIContexts) {
+            try {
+                context.updateDeviceId(deviceId);
+            } catch (IllegalArgumentException e) {
+                // It can happen that the system already closed/removed a virtual device
+                // and the passed deviceId is no longer valid.
+                // TODO(b/263355088): check for validity of deviceId before updating
+                // instead of catching this exception once VDM add an API to validate ids.
+            }
+        }
+    }
+
     @Override
-    public void handleConfigurationChanged(Configuration config) {
+    public void handleConfigurationChanged(Configuration config, int deviceId) {
         mConfigurationController.handleConfigurationChanged(config);
+        updateDeviceIdForNonUIContexts(deviceId);
 
         // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
         mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 309b253..a7a4b35 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3875,4 +3875,19 @@
             throw e.rethrowAsRuntimeException();
         }
     }
+
+    @Override
+    public boolean canUserUninstall(String packageName, UserHandle user) {
+        try {
+            return mPM.getBlockUninstallForUser(packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public boolean shouldShowNewAppInstalledNotification() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 1;
+    }
 }
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index bd9bab3..3ba5783 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -184,8 +184,8 @@
     /** Get package info. */
     public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
 
-    /** Deliver app configuration change notification. */
-    public abstract void handleConfigurationChanged(Configuration config);
+    /** Deliver app configuration change notification and device association. */
+    public abstract void handleConfigurationChanged(Configuration config, int deviceId);
 
     /**
      * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a832b9a..1120257 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2710,7 +2710,7 @@
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
                 overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(),
                 mResources.getLoaders()));
-        context.mDisplay = display;
+        context.setDisplay(display);
         // Inherit context type if the container is from System or System UI context to bypass
         // UI context check.
         context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
@@ -2726,6 +2726,13 @@
         return context;
     }
 
+    private void setDisplay(Display display) {
+        mDisplay = display;
+        if (display != null) {
+            updateDeviceIdIfChanged(display.getDisplayId());
+        }
+    }
+
     @Override
     public @NonNull Context createDeviceContext(int deviceId) {
         if (!isValidDeviceId(deviceId)) {
@@ -2863,8 +2870,8 @@
         baseContext.setResources(windowContextResources);
         // Associate the display with window context resources so that configuration update from
         // the server side will also apply to the display's metrics.
-        baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
-                windowContextResources);
+        baseContext.setDisplay(ResourcesManager.getInstance().getAdjustedDisplay(
+                displayId, windowContextResources));
 
         return baseContext;
     }
@@ -3008,11 +3015,24 @@
 
     @Override
     public void updateDisplay(int displayId) {
-        mDisplay = mResourcesManager.getAdjustedDisplay(displayId, mResources);
+        setDisplay(mResourcesManager.getAdjustedDisplay(displayId, mResources));
         if (mContextType == CONTEXT_TYPE_NON_UI) {
             mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT;
         }
-        // TODO(b/253201821): Update deviceId when display is updated.
+    }
+
+    private void updateDeviceIdIfChanged(int displayId) {
+        if (mIsExplicitDeviceId) {
+            return;
+        }
+        VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
+        if (vdm != null) {
+            int deviceId = vdm.getDeviceIdForDisplayId(displayId);
+            if (deviceId != mDeviceId) {
+                mDeviceId = deviceId;
+                notifyOnDeviceChangedListeners(mDeviceId);
+            }
+        }
     }
 
     @Override
@@ -3307,8 +3327,8 @@
                 classLoader,
                 packageInfo.getApplication() == null ? null
                         : packageInfo.getApplication().getResources().getLoaders()));
-        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
-                context.getResources());
+        context.setDisplay(resourcesManager.getAdjustedDisplay(
+                displayId, context.getResources()));
         return context;
     }
 
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
new file mode 100644
index 0000000..1905b6a
--- /dev/null
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class allow applications to control granular grammatical inflection settings (such as
+ * per-app grammatical gender).
+ */
+@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
+public class GrammaticalInflectionManager {
+    private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+            Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+            Configuration.GRAMMATICAL_GENDER_NEUTRAL,
+            Configuration.GRAMMATICAL_GENDER_FEMININE,
+            Configuration.GRAMMATICAL_GENDER_MASCULINE));
+
+    private final Context mContext;
+    private final IGrammaticalInflectionManager mService;
+
+    /** @hide Instantiated by ContextImpl */
+    public GrammaticalInflectionManager(Context context, IGrammaticalInflectionManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns the current grammatical gender for the calling app. A new value can be requested via
+     * {@link #setRequestedApplicationGrammaticalGender(int)} and will be updated with a new
+     * configuration change. The method always returns the value received with the last received
+     * configuration change.
+     *
+     * @return the value of grammatical gender
+     * @see Configuration#getGrammaticalGender
+     */
+    @Configuration.GrammaticalGender
+    public int getApplicationGrammaticalGender() {
+        return mContext.getApplicationContext()
+                .getResources()
+                .getConfiguration()
+                .getGrammaticalGender();
+    }
+
+    /**
+     * Sets the current grammatical gender for the calling app (keyed by package name and user ID
+     * retrieved from the calling pid).
+     *
+     * <p><b>Note:</b> Changes to app grammatical gender will result in a configuration change (and
+     * potentially an Activity re-creation) being applied to the specified application. For more
+     * information, see the <a
+     * href="https://developer.android.com/guide/topics/resources/runtime-changes">section on
+     * handling configuration changes</a>. The set grammatical gender are persisted across
+     * application restarts; they are backed up if the user has enabled Backup & Restore.`
+     *
+     * @param grammaticalGender the terms of address the user preferred in an application.
+     * @see Configuration#getGrammaticalGender
+     */
+    public void setRequestedApplicationGrammaticalGender(
+            @Configuration.GrammaticalGender int grammaticalGender) {
+        if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+            throw new IllegalArgumentException("Unknown grammatical gender");
+        }
+
+        try {
+            mService.setRequestedApplicationGrammaticalGender(
+                    mContext.getPackageName(), mContext.getUserId(), grammaticalGender);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index f20503c..461aa3c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -198,9 +198,8 @@
      * @param taskId The id of the task to set the bounds for.
      * @param bounds The new bounds.
      * @param resizeMode Resize mode defined as {@code ActivityTaskManager#RESIZE_MODE_*} constants.
-     * @return Return true on success. Otherwise false.
      */
-    boolean resizeTask(int taskId, in Rect bounds, int resizeMode);
+    void resizeTask(int taskId, in Rect bounds, int resizeMode);
     void moveRootTaskToDisplay(int taskId, int displayId);
 
     void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop);
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
new file mode 100644
index 0000000..9366a45
--- /dev/null
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -0,0 +1,19 @@
+package android.app;
+
+
+/**
+ * Internal interface used to control app-specific gender.
+ *
+ * <p>Use the {@link android.app.GrammarInflectionManager} class rather than going through
+ * this Binder interface directly. See {@link android.app.GrammarInflectionManager} for
+ * more complete documentation.
+ *
+ * @hide
+ */
+ interface IGrammaticalInflectionManager {
+
+     /**
+      * Sets a specified app’s app-specific grammatical gender.
+      */
+     void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
+ }
\ No newline at end of file
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index df13a87..5b3b2a6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1547,6 +1547,7 @@
                                 IAmbientContextManager.Stub.asInterface(iBinder);
                         return new AmbientContextManager(ctx.getOuterContext(), manager);
                     }});
+
         registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class,
                 new CachedServiceFetcher<WearableSensingManager>() {
                     @Override
@@ -1559,6 +1560,18 @@
                         return new WearableSensingManager(ctx.getOuterContext(), manager);
                     }});
 
+        registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
+                new CachedServiceFetcher<GrammaticalInflectionManager>() {
+                    @Override
+                    public GrammaticalInflectionManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        return new GrammaticalInflectionManager(ctx,
+                                IGrammaticalInflectionManager.Stub.asInterface(
+                                        ServiceManager.getServiceOrThrow(
+                                                Context.GRAMMATICAL_INFLECTION_SERVICE)));
+                    }});
+
+
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 7255c3e..bad282e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -1041,6 +1042,42 @@
         return backupAgent.getBackupRestoreEventLogger();
     }
 
+    /**
+     * Get an instance of {@link BackupRestoreEventLogger} to report B&R related events during a
+     * delayed restore operation.
+     *
+     * @return an instance of {@link BackupRestoreEventLogger}.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public BackupRestoreEventLogger getDelayedRestoreLogger() {
+        return new BackupRestoreEventLogger(OperationType.RESTORE);
+    }
+
+    /**
+     * Report B&R related events following a delayed restore operation.
+     *
+     * @param logger an instance of {@link BackupRestoreEventLogger} to which the corresponding
+     *               events have been logged.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public void reportDelayedRestoreResult(@NonNull BackupRestoreEventLogger logger) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.reportDelayedRestoreResult(mContext.getPackageName(),
+                        logger.getLoggingResults());
+            } catch (RemoteException e) {
+                Log.w(TAG, "reportDelayedRestoreResult() couldn't connect");
+            }
+        }
+    }
+
     /*
      * We wrap incoming binder calls with a private class implementation that
      * redirects them into main-thread actions.  This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index bf5be95..aeb4987 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupObserver;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -722,4 +723,6 @@
      * that have been excluded will be passed to the agent to make it aware of the exclusions.
      */
     void excludeKeysFromRestore(String packageName, in List<String> keys);
+
+    void reportDelayedRestoreResult(in String packageName, in List<DataTypeResult> results);
 }
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 49a1c66..a563bbc 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -32,6 +32,7 @@
 public class ConfigurationChangeItem extends ClientTransactionItem {
 
     private Configuration mConfiguration;
+    private int mDeviceId;
 
     @Override
     public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
@@ -42,7 +43,7 @@
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
-        client.handleConfigurationChanged(mConfiguration);
+        client.handleConfigurationChanged(mConfiguration, mDeviceId);
     }
 
 
@@ -51,12 +52,13 @@
     private ConfigurationChangeItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ConfigurationChangeItem obtain(Configuration config) {
+    public static ConfigurationChangeItem obtain(Configuration config, int deviceId) {
         ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);
         if (instance == null) {
             instance = new ConfigurationChangeItem();
         }
         instance.mConfiguration = config;
+        instance.mDeviceId = deviceId;
 
         return instance;
     }
@@ -64,6 +66,7 @@
     @Override
     public void recycle() {
         mConfiguration = null;
+        mDeviceId = 0;
         ObjectPool.recycle(this);
     }
 
@@ -74,11 +77,13 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedObject(mConfiguration, flags);
+        dest.writeInt(mDeviceId);
     }
 
     /** Read from Parcel. */
     private ConfigurationChangeItem(Parcel in) {
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mDeviceId = in.readInt();
     }
 
     public static final @android.annotation.NonNull Creator<ConfigurationChangeItem> CREATOR =
@@ -101,16 +106,20 @@
             return false;
         }
         final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
-        return Objects.equals(mConfiguration, other.mConfiguration);
+        return Objects.equals(mConfiguration, other.mConfiguration)
+                && mDeviceId == other.mDeviceId;
     }
 
     @Override
     public int hashCode() {
-        return mConfiguration.hashCode();
+        int result = 17;
+        result = 31 * result + mDeviceId;
+        result = 31 * result + mConfiguration.hashCode();
+        return result;
     }
 
     @Override
     public String toString() {
-        return "ConfigurationChangeItem{config=" + mConfiguration + "}";
+        return "ConfigurationChangeItem{deviceId=" + mDeviceId + ", config" + mConfiguration + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 7e4db81..3d0aa25 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -58,6 +58,7 @@
     private ActivityInfo mInfo;
     private Configuration mCurConfig;
     private Configuration mOverrideConfig;
+    private int mDeviceId;
     private String mReferrer;
     private IVoiceInteractor mVoiceInteractor;
     private int mProcState;
@@ -95,7 +96,7 @@
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
         ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
-                mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
+                mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor, mState, mPersistentState,
                 mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
                 client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
                 mTaskFragmentToken);
@@ -116,7 +117,7 @@
 
     /** Obtain an instance initialized with provided params. */
     public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info,
-            Configuration curConfig, Configuration overrideConfig,
+            Configuration curConfig, Configuration overrideConfig, int deviceId,
             String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
             PersistableBundle persistentState, List<ResultInfo> pendingResults,
             List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -127,7 +128,7 @@
         if (instance == null) {
             instance = new LaunchActivityItem();
         }
-        setValues(instance, intent, ident, info, curConfig, overrideConfig, referrer,
+        setValues(instance, intent, ident, info, curConfig, overrideConfig, deviceId, referrer,
                 voiceInteractor, procState, state, persistentState, pendingResults,
                 pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
                 activityClientController, shareableActivityToken,
@@ -138,7 +139,7 @@
 
     @Override
     public void recycle() {
-        setValues(this, null, 0, null, null, null, null, null, 0, null, null, null, null,
+        setValues(this, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
                 null, false, null, null, null, null, false, null);
         ObjectPool.recycle(this);
     }
@@ -154,6 +155,7 @@
         dest.writeTypedObject(mInfo, flags);
         dest.writeTypedObject(mCurConfig, flags);
         dest.writeTypedObject(mOverrideConfig, flags);
+        dest.writeInt(mDeviceId);
         dest.writeString(mReferrer);
         dest.writeStrongInterface(mVoiceInteractor);
         dest.writeInt(mProcState);
@@ -175,7 +177,7 @@
     private LaunchActivityItem(Parcel in) {
         setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(),
                 in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
-                in.readTypedObject(Configuration.CREATOR), in.readString(),
+                in.readTypedObject(Configuration.CREATOR), in.readInt(), in.readString(),
                 IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
                 in.readBundle(getClass().getClassLoader()),
                 in.readPersistableBundle(getClass().getClassLoader()),
@@ -215,6 +217,7 @@
         return intentsEqual && mIdent == other.mIdent
                 && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
                 && Objects.equals(mOverrideConfig, other.mOverrideConfig)
+                && mDeviceId == other.mDeviceId
                 && Objects.equals(mReferrer, other.mReferrer)
                 && mProcState == other.mProcState && areBundlesEqualRoughly(mState, other.mState)
                 && areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
@@ -235,6 +238,7 @@
         result = 31 * result + mIdent;
         result = 31 * result + Objects.hashCode(mCurConfig);
         result = 31 * result + Objects.hashCode(mOverrideConfig);
+        result = 31 * result + mDeviceId;
         result = 31 * result + Objects.hashCode(mReferrer);
         result = 31 * result + Objects.hashCode(mProcState);
         result = 31 * result + getRoughBundleHashCode(mState);
@@ -279,16 +283,17 @@
     public String toString() {
         return "LaunchActivityItem{intent=" + mIntent + ",ident=" + mIdent + ",info=" + mInfo
                 + ",curConfig=" + mCurConfig + ",overrideConfig=" + mOverrideConfig
-                + ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState
-                + ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
-                + ",pendingNewIntents=" + mPendingNewIntents + ",options=" + mActivityOptions
-                + ",profilerInfo=" + mProfilerInfo + ",assistToken=" + mAssistToken
-                + ",shareableActivityToken=" + mShareableActivityToken + "}";
+                + ",deviceId=" + mDeviceId + ",referrer=" + mReferrer + ",procState=" + mProcState
+                + ",state=" + mState + ",persistentState=" + mPersistentState
+                + ",pendingResults=" + mPendingResults + ",pendingNewIntents=" + mPendingNewIntents
+                + ",options=" + mActivityOptions + ",profilerInfo=" + mProfilerInfo
+                + ",assistToken=" + mAssistToken + ",shareableActivityToken="
+                + mShareableActivityToken + "}";
     }
 
     // Using the same method to set and clear values to make sure we don't forget anything
     private static void setValues(LaunchActivityItem instance, Intent intent, int ident,
-            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+            ActivityInfo info, Configuration curConfig, Configuration overrideConfig, int deviceId,
             String referrer, IVoiceInteractor voiceInteractor,
             int procState, Bundle state, PersistableBundle persistentState,
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -300,6 +305,7 @@
         instance.mInfo = info;
         instance.mCurConfig = curConfig;
         instance.mOverrideConfig = overrideConfig;
+        instance.mDeviceId = deviceId;
         instance.mReferrer = referrer;
         instance.mVoiceInteractor = voiceInteractor;
         instance.mProcState = procState;
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 22ea9f20..9ab7cf9 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -60,62 +60,73 @@
     /**
      * Closes the virtual device and frees all associated resources.
      */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void close();
 
     /**
      * Notifies of an audio session being started.
      */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void onAudioSessionStarting(
             int displayId,
             IAudioRoutingCallback routingCallback,
             IAudioConfigChangedCallback configChangedCallback);
 
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void onAudioSessionEnded();
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualDpad(
             in VirtualDpadConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualKeyboard(
             in VirtualKeyboardConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualMouse(
             in VirtualMouseConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualTouchscreen(
             in VirtualTouchscreenConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualNavigationTouchpad(
             in VirtualNavigationTouchpadConfig config,
             IBinder token);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterInputDevice(IBinder token);
     int getInputDeviceId(IBinder token);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
      * Creates a virtual sensor, capable of injecting sensor events into the system.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
 
     /**
      * Removes the sensor corresponding to the given token from the system.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterSensor(IBinder token);
 
     /**
      * Sends an event to the virtual sensor corresponding to the given token.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
 
     /**
@@ -126,6 +137,7 @@
     PointF getCursorPosition(IBinder token);
 
     /** Sets whether to show or hide the cursor while this virtual device is active. */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void setShowPointerIcon(boolean showPointerIcon);
 
     /**
@@ -133,9 +145,9 @@
      * when matching the provided IntentFilter and calls the callback with the intercepted
      * intent.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void registerIntentInterceptor(
             in IVirtualDeviceIntentInterceptor intentInterceptor, in IntentFilter filter);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 8561018..adf59fb 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -775,13 +775,11 @@
             final Point size = new Point();
             display.getDisplay().getSize(size);
             VirtualTouchscreenConfig touchscreenConfig =
-                    new VirtualTouchscreenConfig.Builder()
+                    new VirtualTouchscreenConfig.Builder(size.x, size.y)
                             .setVendorId(vendorId)
                             .setProductId(productId)
                             .setInputDeviceName(inputDeviceName)
                             .setAssociatedDisplayId(display.getDisplay().getDisplayId())
-                            .setWidthInPixels(size.x)
-                            .setHeightInPixels(size.y)
                             .build();
             return createVirtualTouchscreen(touchscreenConfig);
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9c25c32..7d7232e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
 import android.app.GameManager;
+import android.app.GrammaticalInflectionManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
@@ -3973,6 +3974,8 @@
             CREDENTIAL_SERVICE,
             DEVICE_LOCK_SERVICE,
             VIRTUALIZATION_SERVICE,
+            GRAMMATICAL_INFLECTION_SERVICE,
+
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -6169,6 +6172,14 @@
     public static final String VIRTUALIZATION_SERVICE = "virtualization";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link GrammaticalInflectionManager}.
+     *
+     * @see #getSystemService(String)
+     */
+    public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -7280,7 +7291,9 @@
      * Updates the device ID association of this Context. Since a Context created with
      * {@link #createDeviceContext} cannot change its device association, this method must
      * not be called for instances created with {@link #createDeviceContext}.
-     *
+     *<p>
+     * Note that updating the deviceId of the Context will not update its associated display.
+     *</p>
      * @param deviceId The new device ID to assign to this Context.
      * @throws UnsupportedOperationException if the method is called on an instance that was
      *         created with {@link Context#createDeviceContext(int)}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8aa0454..265e02a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1814,8 +1814,8 @@
      * Package manager install result code.  @hide because result codes are not
      * yet ready to be exposed.
      */
-    public static final String EXTRA_INSTALL_RESULT
-            = "android.intent.extra.INSTALL_RESULT";
+    @SystemApi
+    public static final String EXTRA_INSTALL_RESULT = "android.intent.extra.INSTALL_RESULT";
 
     /**
      * Activity Action: Launch application uninstaller.
@@ -1841,6 +1841,7 @@
      * Specify whether the package should be uninstalled for all users.
      * @hide because these should not be part of normal application flow.
      */
+    @SystemApi
     public static final String EXTRA_UNINSTALL_ALL_USERS
             = "android.intent.extra.UNINSTALL_ALL_USERS";
 
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index dab57fd..68a84e8 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -807,6 +807,7 @@
             CONFIG_LAYOUT_DIRECTION,
             CONFIG_COLOR_MODE,
             CONFIG_FONT_SCALE,
+            CONFIG_GRAMMATICAL_GENDER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Config {}
@@ -917,6 +918,12 @@
     public static final int CONFIG_COLOR_MODE = 0x4000;
     /**
      * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle the change to gender. Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_GRAMMATICAL_GENDER = 0x8000;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
      * can itself handle asset path changes.  Set from the {@link android.R.attr#configChanges}
      * attribute. This is not a core resource configuration, but a higher-level value, so its
      * constant starts at the high bits.
@@ -946,7 +953,6 @@
      * not a core resource configuration, but a higher-level value, so its
      * constant starts at the high bits.
      */
-
     public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000;
 
     /** @hide
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 84811ea..94c5e25 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -18,9 +18,11 @@
 
 import static android.os.Build.VERSION_CODES.DONUT;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -2253,6 +2255,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
     public boolean hasFragileUserData() {
         return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0;
     }
@@ -2487,8 +2491,13 @@
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY) != 0;
     }
 
-    /** @hide */
+    /**
+     * @return {@code true} if the application is permitted to hold privileged permissions.
+     *
+     * @hide */
     @TestApi
+    @SystemApi
+    @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
     public boolean isPrivilegedApp() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index febdaed..703a9252 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -26,6 +26,9 @@
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_AUTO;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -48,6 +51,10 @@
 import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.PackageManager.InstallReason;
 import android.content.pm.PackageManager.InstallScenario;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.graphics.Bitmap;
 import android.icu.util.ULocale;
 import android.net.Uri;
@@ -70,12 +77,14 @@
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.Closeable;
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -172,10 +181,22 @@
     public static final String ACTION_SESSION_UPDATED =
             "android.content.pm.action.SESSION_UPDATED";
 
-    /** {@hide} */
+    /**
+     * Intent action to indicate that user action is required for current install. This action can
+     * be used only by system apps.
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
 
-    /** @hide */
+    /**
+     * Activity Action: Intent sent to the installer when a session for requesting
+     * user pre-approval, and user needs to confirm the installation.
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String ACTION_CONFIRM_PRE_APPROVAL =
             "android.content.pm.action.CONFIRM_PRE_APPROVAL";
 
@@ -272,11 +293,23 @@
     @Deprecated
     public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
 
-    /** {@hide} */
+    /**
+     * The status as used internally in the package manager. Refer to {@link PackageManager} for
+     * a list of all valid legacy statuses.
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     /** {@hide} */
     public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE";
-    /** {@hide} */
+    /**
+     * The callback to execute once an uninstall is completed (used for both successful and
+     * unsuccessful uninstalls).
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
 
     /**
@@ -293,6 +326,17 @@
     public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
 
     /**
+     * Path to the validated base APK for this session, which may point at an
+     * APK inside the session (when the session defines the base), or it may
+     * point at the existing base APK (when adding splits to an existing app).
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_RESOLVED_BASE_PATH =
+            "android.content.pm.extra.RESOLVED_BASE_PATH";
+
+    /**
      * Streaming installation pending.
      * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
      *
@@ -796,8 +840,6 @@
      * @param statusReceiver Where to deliver the result of the operation indicated by the extra
      *                       {@link #EXTRA_STATUS}. Refer to the individual status codes
      *                       on how to handle them.
-     *
-     * @hide
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.DELETE_PACKAGES,
@@ -1871,6 +1913,101 @@
     }
 
     /**
+     * Parse a single APK or a directory of APKs to get install relevant information about
+     * the package wrapped in {@link InstallInfo}.
+     * @throws PackageParsingException if the package source file(s) provided is(are) not valid,
+     * or the parser isn't able to parse the supplied source(s).
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public InstallInfo getInstallInfo(@NonNull File file, int flags)
+            throws PackageParsingException {
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                input.reset(), file, flags);
+        if (result.isError()) {
+            throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage());
+        }
+        return new InstallInfo(result);
+    }
+
+    // (b/239722738) This class serves as a bridge between the PackageLite class, which
+    // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
+    // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
+    // public APIs.
+    /**
+     * Install related details from an APK or a folder of APK(s).
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class InstallInfo {
+
+        /** @hide */
+        @IntDef(prefix = { "INSTALL_LOCATION_" }, value = {
+                INSTALL_LOCATION_AUTO,
+                INSTALL_LOCATION_INTERNAL_ONLY,
+                INSTALL_LOCATION_PREFER_EXTERNAL
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface InstallLocation{}
+
+        private PackageLite mPkg;
+
+        InstallInfo(ParseResult<PackageLite> result) {
+            mPkg = result.getResult();
+        }
+
+        /**
+         * See {@link PackageLite#getPackageName()}
+         */
+        @NonNull
+        public String getPackageName() {
+            return mPkg.getPackageName();
+        }
+
+        /**
+         * @return The default install location defined by an application in
+         * {@link android.R.attr#installLocation} attribute.
+         */
+        public @InstallLocation int getInstallLocation() {
+            return mPkg.getInstallLocation();
+        }
+
+        /**
+         * @param params {@link SessionParams} of the installation
+         * @return Total disk space occupied by an application after installation.
+         * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
+         * and all relevant native code.
+         * @throws IOException when size of native binaries cannot be calculated.
+         */
+        public long calculateInstalledSize(@NonNull SessionParams params) throws IOException {
+            return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride);
+        }
+    }
+
+    /**
+     * Generic exception class for using with parsing operations.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class PackageParsingException extends Exception {
+        private final int mErrorCode;
+
+        /** {@hide} */
+        public PackageParsingException(int errorCode, @Nullable String detailedMessage) {
+            super(detailedMessage);
+            mErrorCode = errorCode;
+        }
+
+        public int getErrorCode() {
+            return mErrorCode;
+        }
+    }
+
+    /**
      * Parameters for creating a new {@link PackageInstaller.Session}.
      */
     public static class SessionParams implements Parcelable {
@@ -2431,9 +2568,7 @@
          * By default this is the app that created the {@link PackageInstaller} object.
          *
          * @param installerPackageName name of the installer package
-         * {@hide}
          */
-        @TestApi
         public void setInstallerPackageName(@Nullable String installerPackageName) {
             this.installerPackageName = installerPackageName;
         }
@@ -3430,8 +3565,6 @@
 
         /**
          * Returns the Uid of the owner of the session.
-         *
-         * @hide
          */
         public int getInstallerUid() {
             return installerUid;
@@ -3445,6 +3578,13 @@
             return keepApplicationEnabledSetting;
         }
 
+        /**
+         * Returns whether this session has requested user pre-approval.
+         */
+        public @NonNull boolean getIsPreApprovalRequested() {
+            return isPreapprovalRequested;
+        }
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index cbdcc02..4ad657e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2250,6 +2250,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_KEEP_DATA = 0x00000001;
 
     /**
@@ -2258,6 +2259,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_ALL_USERS = 0x00000002;
 
     /**
@@ -2295,6 +2297,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_SUCCEEDED = 1;
 
     /**
@@ -2304,6 +2307,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
 
     /**
@@ -2313,6 +2317,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
 
     /**
@@ -2332,9 +2337,11 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
 
     /** {@hide} */
+    @SystemApi
     public static final int DELETE_FAILED_ABORTED = -5;
 
     /**
@@ -4090,6 +4097,17 @@
     public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the requisite kernel support for migrating IPsec tunnels to new source/destination addresses.
+     *
+     * <p>This feature implies that the device supports XFRM Migration (CONFIG_XFRM_MIGRATE) and has
+     * the kernel fixes to support cross-address-family IPsec tunnel migration
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+            "android.software.ipsec_tunnel_migration";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports a system interface for the user to select
      * and bind device control services provided by applications.
@@ -5336,17 +5354,7 @@
             throws NameNotFoundException;
 
     /**
-     * Return the UID associated with the given package name.
-     * <p>
-     * Note that the same package will have different UIDs under different
-     * {@link UserHandle} on the same device.
-     *
-     * @param packageName The full name (i.e. com.google.apps.contacts) of the
-     *            desired package.
-     * @param userId The user handle identifier to look up the package under.
-     * @return Returns an integer UID who owns the given package name.
-     * @throws NameNotFoundException if no such package is available to the
-     *             caller.
+     * See {@link #getPackageUidAsUser(String, PackageInfoFlags, int)}.
      * @deprecated Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} instead.
      * @hide
      */
@@ -5357,9 +5365,22 @@
             int flags, @UserIdInt int userId) throws NameNotFoundException;
 
     /**
-     * See {@link #getPackageUidAsUser(String, int, int)}.
+     * Return the UID associated with the given package name.
+     * <p>
+     * Note that the same package will have different UIDs under different
+     * {@link UserHandle} on the same device.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of the
+     *            desired package.
+     * @param flags Additional option flags to modify the data returned.
+     * @param userId The user handle identifier to look up the package under.
+     * @return Returns an integer UID who owns the given package name.
+     * @throws NameNotFoundException if no such package is available to the
+     *             caller.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
     public int getPackageUidAsUser(@NonNull String packageName, @NonNull PackageInfoFlags flags,
             @UserIdInt int userId) throws NameNotFoundException {
         throw new UnsupportedOperationException(
@@ -9805,6 +9826,83 @@
     }
 
     /**
+     * A parcelable class to pass as an intent extra to the PackageInstaller. When an uninstall is
+     * completed (both successfully or unsuccessfully), the result is sent to the uninstall
+     * initiators.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class UninstallCompleteCallback implements Parcelable {
+        private IPackageDeleteObserver2 mBinder;
+
+        /** @hide */
+        @IntDef(prefix = { "DELETE_" }, value = {
+                DELETE_SUCCEEDED,
+                DELETE_FAILED_INTERNAL_ERROR,
+                DELETE_FAILED_DEVICE_POLICY_MANAGER,
+                DELETE_FAILED_USER_RESTRICTED,
+                DELETE_FAILED_OWNER_BLOCKED,
+                DELETE_FAILED_ABORTED,
+                DELETE_FAILED_USED_SHARED_LIBRARY,
+                DELETE_FAILED_APP_PINNED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DeleteStatus{}
+
+        /** @hide */
+        public UninstallCompleteCallback(@NonNull IBinder binder) {
+            mBinder = IPackageDeleteObserver2.Stub.asInterface(binder);
+        }
+
+        /** @hide */
+        private UninstallCompleteCallback(Parcel in) {
+            mBinder = IPackageDeleteObserver2.Stub.asInterface(in.readStrongBinder());
+        }
+
+        public static final @NonNull Parcelable.Creator<UninstallCompleteCallback> CREATOR =
+                new Parcelable.Creator<>() {
+                    public UninstallCompleteCallback createFromParcel(Parcel source) {
+                        return new UninstallCompleteCallback(source);
+                    }
+
+                    public UninstallCompleteCallback[] newArray(int size) {
+                        return new UninstallCompleteCallback[size];
+                    }
+                };
+
+        /**
+         * Called when an uninstallation is completed successfully or unsuccessfully.
+         *
+         * @param packageName The name of the package being uninstalled.
+         * @param resultCode Result code of the operation.
+         * @param errorMessage Error message if any.
+         *
+         * @hide */
+        @SystemApi
+        public void onUninstallComplete(@NonNull String packageName, @DeleteStatus int resultCode,
+                @Nullable String errorMessage) {
+            try {
+                mBinder.onPackageDeleted(packageName, resultCode, errorMessage);
+            } catch (RemoteException e) {
+                // no-op
+            }
+        }
+
+        /** @hide */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /** @hide */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeStrongBinder(mBinder.asBinder());
+        }
+    }
+
+    /**
      * Return the install reason that was recorded when a package was first
      * installed for a specific user. Requesting the install reason for another
      * user will require the permission INTERACT_ACROSS_USERS_FULL.
@@ -10723,4 +10821,33 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Checks if a package is blocked from uninstall for a particular user. A package can be
+     * blocked from being uninstalled by a device owner or profile owner.
+     * See {@link DevicePolicyManager#setUninstallBlocked(ComponentName, String, boolean)}.
+     *
+     * @param packageName Name of the package being uninstalled.
+     * @param user UserHandle who's ability to uninstall a package is being checked.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public boolean canUserUninstall(@NonNull String packageName, @NonNull UserHandle user){
+        throw new UnsupportedOperationException(
+                "canUserUninstall not implemented in subclass");
+    }
+
+    /**
+     * See {@link android.provider.Settings.Global#SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public boolean shouldShowNewAppInstalledNotification() {
+        throw new UnsupportedOperationException(
+                "isShowNewAppInstalledNotificationEnabled not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index f47c1e0..96aa624 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -46,6 +46,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.app.GrammaticalInflectionManager;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.LocaleProto;
@@ -141,6 +142,44 @@
     @UnsupportedAppUsage
     public boolean userSetLocale;
 
+    /**
+     * Current user preference for the grammatical gender.
+     */
+    @GrammaticalGender
+    private int mGrammaticalGender;
+
+    /** @hide */
+    @IntDef(prefix = { "GRAMMATICAL_GENDER_" }, value = {
+            GRAMMATICAL_GENDER_NOT_SPECIFIED,
+            GRAMMATICAL_GENDER_NEUTRAL,
+            GRAMMATICAL_GENDER_FEMININE,
+            GRAMMATICAL_GENDER_MASCULINE,
+    })
+    public @interface GrammaticalGender {}
+
+    /**
+     * Constant for grammatical gender: to indicate the user has not specified the terms
+     * of address for the application.
+     */
+    public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0;
+
+    /**
+     * Constant for grammatical gender: to indicate the terms of address the user
+     * preferred in an application is neuter.
+     */
+    public static final int GRAMMATICAL_GENDER_NEUTRAL = 2;
+
+    /**
+     * Constant for grammatical gender: to indicate the terms of address the user
+         * preferred in an application is feminine.
+     */
+    public static final int GRAMMATICAL_GENDER_FEMININE = 3;
+
+    /**
+     * Constant for grammatical gender: to indicate the terms of address the user
+     * preferred in an application is masculine.
+     */
+    public static final int GRAMMATICAL_GENDER_MASCULINE = 4;
 
     /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
     public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
@@ -1024,6 +1063,7 @@
         }
         o.fixUpLocaleList();
         mLocaleList = o.mLocaleList;
+        mGrammaticalGender = o.mGrammaticalGender;
         userSetLocale = o.userSetLocale;
         touchscreen = o.touchscreen;
         keyboard = o.keyboard;
@@ -1510,6 +1550,7 @@
         seq = 0;
         windowConfiguration.setToDefaults();
         fontWeightAdjustment = FONT_WEIGHT_ADJUSTMENT_UNDEFINED;
+        mGrammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
     }
 
     /**
@@ -1712,6 +1753,10 @@
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
             fontWeightAdjustment = delta.fontWeightAdjustment;
         }
+        if (delta.mGrammaticalGender != mGrammaticalGender) {
+            changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
+            mGrammaticalGender = delta.mGrammaticalGender;
+        }
 
         return changed;
     }
@@ -1929,6 +1974,10 @@
                 && fontWeightAdjustment != delta.fontWeightAdjustment) {
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
         }
+
+        if (!publicOnly&& mGrammaticalGender != delta.mGrammaticalGender) {
+            changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
+        }
         return changed;
     }
 
@@ -2023,6 +2072,7 @@
         dest.writeInt(assetsSeq);
         dest.writeInt(seq);
         dest.writeInt(fontWeightAdjustment);
+        dest.writeInt(mGrammaticalGender);
     }
 
     public void readFromParcel(Parcel source) {
@@ -2055,6 +2105,7 @@
         assetsSeq = source.readInt();
         seq = source.readInt();
         fontWeightAdjustment = source.readInt();
+        mGrammaticalGender = source.readInt();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<Configuration> CREATOR
@@ -2155,6 +2206,8 @@
         if (n != 0) return n;
         n = this.fontWeightAdjustment - that.fontWeightAdjustment;
         if (n != 0) return n;
+        n = this.mGrammaticalGender - that.mGrammaticalGender;
+        if (n != 0) return n;
 
         // if (n != 0) return n;
         return n;
@@ -2196,10 +2249,37 @@
         result = 31 * result + densityDpi;
         result = 31 * result + assetsSeq;
         result = 31 * result + fontWeightAdjustment;
+        result = 31 * result + mGrammaticalGender;
         return result;
     }
 
     /**
+     * Returns the user preference for the grammatical gender. Will be
+     * {@link #GRAMMATICAL_GENDER_NOT_SPECIFIED} or
+     * {@link #GRAMMATICAL_GENDER_NEUTRAL} or
+     * {@link #GRAMMATICAL_GENDER_FEMININE} or
+     * {@link #GRAMMATICAL_GENDER_MASCULINE}.
+     *
+     * @return The preferred grammatical gender.
+     */
+    @GrammaticalGender
+    public int getGrammaticalGender() {
+        return mGrammaticalGender;
+    }
+
+    /**
+     * Sets the user preference for the grammatical gender. This is only for frameworks to easily
+     * override the gender in the configuration. To update the grammatical gender for an application
+     * use {@link GrammaticalInflectionManager#setRequestedApplicationGrammaticalGender(int)}.
+     *
+     * @param grammaticalGender The preferred grammatical gender.
+     * @hide
+     */
+    public void setGrammaticalGender(@GrammaticalGender int grammaticalGender) {
+        mGrammaticalGender = grammaticalGender;
+    }
+
+    /**
      * Get the locale list. This is the preferred way for getting the locales (instead of using
      * the direct accessor to {@link #locale}, which would only provide the primary locale).
      *
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 0a14574..b8b1eaa 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -521,9 +521,7 @@
     public static final int DATASPACE_BT2020_HLG = 168165376;
 
     /**
-     * ITU-R Recommendation 2020 (BT.2020)
-     *
-     * Ultra High-definition television.
+     * Perceptual Quantizer encoding.
      *
      * <p>Composed of the following -</p>
      * <pre>
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9c42160..655e598 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -166,6 +166,14 @@
      * The <code>android:keyboardLayout</code> attribute refers to a
      * <a href="http://source.android.com/tech/input/key-character-map-files.html">
      * key character map</a> resource that defines the keyboard layout.
+     * The <code>android:keyboardLocale</code> attribute specifies a comma separated list of BCP 47
+     * language tags depicting the locales supported by the keyboard layout. This attribute is
+     * optional and will be used for auto layout selection for external physical keyboards.
+     * The <code>android:keyboardLayoutType</code> attribute specifies the layoutType for the
+     * keyboard layout. This can be either empty or one of the following supported layout types:
+     * qwerty, qwertz, azerty, dvorak, colemak, workman, extended, turkish_q, turkish_f. This
+     * attribute is optional and will be used for auto layout selection for external physical
+     * keyboards.
      * </p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -705,6 +713,30 @@
     }
 
     /**
+     * Returns the layout type of the queried layout
+     * <p>
+     * The input manager consults the built-in keyboard layouts as well as all keyboard layouts
+     * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
+     * </p>
+     *
+     * @param layoutDescriptor The layout descriptor of the queried layout
+     * @return layout type of the queried layout
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
+        KeyboardLayout[] layouts = getKeyboardLayouts();
+        for (KeyboardLayout kl : layouts) {
+            if (layoutDescriptor.equals(kl.getDescriptor())) {
+                return kl.getLayoutType();
+            }
+        }
+        return "";
+    }
+
+    /**
      * Gets information about all supported keyboard layouts appropriate
      * for a specific input device.
      * <p>
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 52c1551..58f7759 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -21,24 +21,74 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Describes a keyboard layout.
  *
  * @hide
  */
-public final class KeyboardLayout implements Parcelable,
-        Comparable<KeyboardLayout> {
+public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> {
     private final String mDescriptor;
     private final String mLabel;
     private final String mCollection;
     private final int mPriority;
     @NonNull
     private final LocaleList mLocales;
+    private final LayoutType mLayoutType;
     private final int mVendorId;
     private final int mProductId;
 
-    public static final @android.annotation.NonNull Parcelable.Creator<KeyboardLayout> CREATOR =
-            new Parcelable.Creator<KeyboardLayout>() {
+    /** Currently supported Layout types in the KCM files */
+    private enum LayoutType {
+        UNDEFINED(0, "undefined"),
+        QWERTY(1, "qwerty"),
+        QWERTZ(2, "qwertz"),
+        AZERTY(3, "azerty"),
+        DVORAK(4, "dvorak"),
+        COLEMAK(5, "colemak"),
+        WORKMAN(6, "workman"),
+        TURKISH_F(7, "turkish_f"),
+        TURKISH_Q(8, "turkish_q"),
+        EXTENDED(9, "extended");
+
+        private final int mValue;
+        private final String mName;
+        private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
+        static {
+            VALUE_TO_ENUM_MAP.put(UNDEFINED.mValue, UNDEFINED);
+            VALUE_TO_ENUM_MAP.put(QWERTY.mValue, QWERTY);
+            VALUE_TO_ENUM_MAP.put(QWERTZ.mValue, QWERTZ);
+            VALUE_TO_ENUM_MAP.put(AZERTY.mValue, AZERTY);
+            VALUE_TO_ENUM_MAP.put(DVORAK.mValue, DVORAK);
+            VALUE_TO_ENUM_MAP.put(COLEMAK.mValue, COLEMAK);
+            VALUE_TO_ENUM_MAP.put(WORKMAN.mValue, WORKMAN);
+            VALUE_TO_ENUM_MAP.put(TURKISH_F.mValue, TURKISH_F);
+            VALUE_TO_ENUM_MAP.put(TURKISH_Q.mValue, TURKISH_Q);
+            VALUE_TO_ENUM_MAP.put(EXTENDED.mValue, EXTENDED);
+        }
+
+        private static LayoutType of(int value) {
+            return VALUE_TO_ENUM_MAP.getOrDefault(value, UNDEFINED);
+        }
+
+        LayoutType(int value, String name) {
+            this.mValue = value;
+            this.mName = name;
+        }
+
+        private int getValue() {
+            return mValue;
+        }
+
+        private String getName() {
+            return mName;
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<KeyboardLayout> CREATOR = new Parcelable.Creator<>() {
         public KeyboardLayout createFromParcel(Parcel source) {
             return new KeyboardLayout(source);
         }
@@ -48,12 +98,13 @@
     };
 
     public KeyboardLayout(String descriptor, String label, String collection, int priority,
-            LocaleList locales, int vid, int pid) {
+            LocaleList locales, int layoutValue, int vid, int pid) {
         mDescriptor = descriptor;
         mLabel = label;
         mCollection = collection;
         mPriority = priority;
         mLocales = locales;
+        mLayoutType = LayoutType.of(layoutValue);
         mVendorId = vid;
         mProductId = pid;
     }
@@ -64,6 +115,7 @@
         mCollection = source.readString();
         mPriority = source.readInt();
         mLocales = LocaleList.CREATOR.createFromParcel(source);
+        mLayoutType = LayoutType.of(source.readInt());
         mVendorId = source.readInt();
         mProductId = source.readInt();
     }
@@ -106,6 +158,15 @@
     }
 
     /**
+     * Gets the layout type that this keyboard layout is intended for.
+     * This may be "undefined" if a layoutType has not been assigned to this keyboard layout.
+     * @return The keyboard layout's intended layout type.
+     */
+    public String getLayoutType() {
+        return mLayoutType.getName();
+    }
+
+    /**
      * Gets the vendor ID of the hardware device this keyboard layout is intended for.
      * Returns -1 if this is not specific to any piece of hardware.
      * @return The hardware vendor ID of the keyboard layout's intended device.
@@ -135,6 +196,7 @@
         dest.writeString(mCollection);
         dest.writeInt(mPriority);
         mLocales.writeToParcel(dest, 0);
+        dest.writeInt(mLayoutType.getValue());
         dest.writeInt(mVendorId);
         dest.writeInt(mProductId);
     }
@@ -160,6 +222,7 @@
                 + ", descriptor: " + mDescriptor
                 + ", priority: " + mPriority
                 + ", locales: " + mLocales.toString()
+                + ", layout type: " + mLayoutType.getName()
                 + ", vendorId: " + mVendorId
                 + ", productId: " + mProductId;
     }
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
index f2805bb..5ad5fd9 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -88,12 +88,17 @@
      * Builder for creating a {@link VirtualNavigationTouchpadConfig}.
      */
     public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
-
         private final int mHeight;
         private final int mWidth;
 
-        public Builder(@IntRange(from = 1) int touchpadHeight,
-                @IntRange(from = 1) int touchpadWidth) {
+        /**
+         * Creates a new instance for the given dimensions of the {@link VirtualNavigationTouchpad}.
+         *
+         * @param touchpadWidth The width of the touchpad.
+         * @param touchpadHeight The height of the touchpad.
+         */
+        public Builder(@IntRange(from = 1) int touchpadWidth,
+                @IntRange(from = 1) int touchpadHeight) {
             if (touchpadHeight <= 0 || touchpadWidth <= 0) {
                 throw new IllegalArgumentException(
                         "Cannot create a virtual navigation touchpad, touchpad dimensions must be "
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
index e358619..aac341cc 100644
--- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -16,6 +16,7 @@
 
 package android.hardware.input;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -29,31 +30,31 @@
 @SystemApi
 public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable {
 
-    /** The touchscreen width in pixels. */
-    private final int mWidthInPixels;
-    /** The touchscreen height in pixels. */
-    private final int mHeightInPixels;
+    /** The touchscreen width. */
+    private final int mWidth;
+    /** The touchscreen height. */
+    private final int mHeight;
 
     private VirtualTouchscreenConfig(@NonNull Builder builder) {
         super(builder);
-        mWidthInPixels = builder.mWidthInPixels;
-        mHeightInPixels = builder.mHeightInPixels;
+        mWidth = builder.mWidth;
+        mHeight = builder.mHeight;
     }
 
     private VirtualTouchscreenConfig(@NonNull Parcel in) {
         super(in);
-        mWidthInPixels = in.readInt();
-        mHeightInPixels = in.readInt();
+        mWidth = in.readInt();
+        mHeight = in.readInt();
     }
 
-    /** Returns the touchscreen width in pixels. */
-    public int getWidthInPixels() {
-        return mWidthInPixels;
+    /** Returns the touchscreen width. */
+    public int getWidth() {
+        return mWidth;
     }
 
-    /** Returns the touchscreen height in pixels. */
-    public int getHeightInPixels() {
-        return mHeightInPixels;
+    /** Returns the touchscreen height. */
+    public int getHeight() {
+        return mHeight;
     }
 
     @Override
@@ -64,8 +65,8 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeInt(mWidthInPixels);
-        dest.writeInt(mHeightInPixels);
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
     }
 
     @NonNull
@@ -86,25 +87,29 @@
      * Builder for creating a {@link VirtualTouchscreenConfig}.
      */
     public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
-        private int mWidthInPixels;
-        private int mHeightInPixels;
+        private int mWidth;
+        private int mHeight;
 
         /**
-         * @see VirtualTouchscreenConfig#getWidthInPixels().
+         * Creates a new instance for the given dimensions of the {@link VirtualTouchscreen}.
+         *
+         * <p>The dimensions are not pixels but in the touchscreens raw coordinate space. They do
+         * not necessarily have to correspond to the display size or aspect ratio. In this case the
+         * framework will handle the scaling appropriately.
+         *
+         * @param touchscreenWidth The width of the touchscreen.
+         * @param touchscreenHeight The height of the touchscreen.
          */
-        @NonNull
-        public Builder setWidthInPixels(int widthInPixels) {
-            mWidthInPixels = widthInPixels;
-            return this;
-        }
-
-        /**
-         * @see VirtualTouchscreenConfig#getHeightInPixels().
-         */
-        @NonNull
-        public Builder setHeightInPixels(int heightInPixels) {
-            mHeightInPixels = heightInPixels;
-            return this;
+        public Builder(@IntRange(from = 1) int touchscreenWidth,
+                @IntRange(from = 1) int touchscreenHeight) {
+            if (touchscreenHeight <= 0 || touchscreenWidth <= 0) {
+                throw new IllegalArgumentException(
+                        "Cannot create a virtual touchscreen, touchscreen dimensions must be "
+                                + "positive. Got: (" + touchscreenHeight + ", "
+                                + touchscreenWidth + ")");
+            }
+            mHeight = touchscreenHeight;
+            mWidth = touchscreenWidth;
         }
 
         /**
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 7faa285..727716e 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -65,12 +65,12 @@
      */
     @Deprecated
     public static final int PROGRAM_TYPE_INVALID = 0;
-    /** Analogue AM radio (with or without RDS).
+    /** Analog AM radio (with or without RDS).
      * @deprecated use {@link ProgramIdentifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_AM = 1;
-    /** analogue FM radio (with or without RDS).
+    /** analog FM radio (with or without RDS).
      * @deprecated use {@link ProgramIdentifier} instead
      */
     @Deprecated
@@ -125,25 +125,50 @@
     public @interface ProgramType {}
 
     public static final int IDENTIFIER_TYPE_INVALID = 0;
-    /** kHz */
+    /**
+     * Primary identifier for analog (without RDS) AM/FM stations:
+     * frequency in kHz.
+     *
+     * <p>This identifier also contains band information:
+     * <li>
+     *     <ul><500kHz: AM LW.
+     *     <ul>500kHz - 1705kHz: AM MW.
+     *     <ul>1.71MHz - 30MHz: AM SW.
+     *     <ul>>60MHz: FM.
+     * </li>
+     */
     public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
-    /** 16bit */
+    /**
+     * 16bit primary identifier for FM RDS station.
+     */
     public static final int IDENTIFIER_TYPE_RDS_PI = 2;
     /**
      * 64bit compound primary identifier for HD Radio.
      *
-     * Consists of (from the LSB):
-     * - 32bit: Station ID number;
-     * - 4bit: HD_SUBCHANNEL;
-     * - 18bit: AMFM_FREQUENCY.
-     * The remaining bits should be set to zeros when writing on the chip side
+     * <p>Consists of (from the LSB):
+     * <li>
+     *     <ul>132bit: Station ID number.
+     *     <ul>14bit: HD_SUBCHANNEL.
+     *     <ul>18bit: AMFM_FREQUENCY.
+     * </li>
+     *
+     * <p>While station ID number should be unique globally, it sometimes gets
+     * abused by broadcasters (i.e. not being set at all). To ensure local
+     * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
+     * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}.
+     *
+     * <p>HD Radio subchannel is a value in range of 0-7.
+     * This index is 0-based (where 0 is MPS and 1..7 are SPS),
+     * as opposed to HD Radio standard (where it's 1-based).
+     *
+     * <p>The remaining bits should be set to zeros when writing on the chip side
      * and ignored when read.
      */
     public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
     /**
-     * HD Radio subchannel - a value of range 0-7.
+     * HD Radio subchannel - a value in range of 0-7.
      *
-     * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
+     * <p>The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
      * as opposed to HD Radio standard (where it's 1-based).
      *
      * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
@@ -153,16 +178,16 @@
     /**
      * 64bit additional identifier for HD Radio.
      *
-     * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+     * <p>Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
      * globally unique. To provide a best-effort solution, a short version of
      * station name may be carried as additional identifier and may be used
      * by the tuner hardware to double-check tuning.
      *
-     * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
-     * must be converted to uppercase). Encoded in little-endian ASCII:
-     * the first character of the name is the LSB.
+     * <p>The name is limited to the first 8 A-Z0-9 characters (lowercase
+     * letters must be converted to uppercase). Encoded in little-endian
+     * ASCII: the first character of the name is the LSB.
      *
-     * For example: "Abc" is encoded as 0x434241.
+     * <p>For example: "Abc" is encoded as 0x434241.
      */
     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
     /**
@@ -175,17 +200,19 @@
     /**
      * 28bit compound primary identifier for Digital Audio Broadcasting.
      *
-     * Consists of (from the LSB):
-     * - 16bit: SId;
-     * - 8bit: ECC code;
-     * - 4bit: SCIdS.
+     * <p>Consists of (from the LSB):
+     * <li>
+     *     <ul>16bit: SId.
+     *     <ul>8bit: ECC code.
+     *     <ul>4bit: SCIdS.
+     * </li>
      *
-     * SCIdS (Service Component Identifier within the Service) value
+     * <p>SCIdS (Service Component Identifier within the Service) value
      * of 0 represents the main service, while 1 and above represents
      * secondary services.
      *
-     * The remaining bits should be set to zeros when writing on the chip side
-     * and ignored when read.
+     * <p>The remaining bits should be set to zeros when writing on the chip
+     * side and ignored when read.
      *
      * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
@@ -197,7 +224,9 @@
     public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
     /** kHz */
     public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
-    /** 24bit */
+    /**
+     * 24bit primary identifier for Digital Radio Mondiale.
+     */
     public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
     /** kHz */
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
@@ -207,7 +236,9 @@
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
-    /** 32bit */
+    /**
+     * 32bit primary identifier for SiriusXM Satellite Radio.
+     */
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /** 0-999 range */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
@@ -224,15 +255,15 @@
      * of 0 represents the main service, while 1 and above represents
      * secondary services.
      *
-     * The remaining bits should be set to zeros when writing on the chip side
-     * and ignored when read.
+     * <p>The remaining bits should be set to zeros when writing on the chip
+     * side and ignored when read.
      */
     public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
     /**
      * Primary identifier for vendor-specific radio technology.
      * The value format is determined by a vendor.
      *
-     * It must not be used in any other programType than corresponding VENDOR
+     * <p>It must not be used in any other programType than corresponding VENDOR
      * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
      * not be used in any program type other than 1015).
      */
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index a83dea3..fb8f84a 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -27,7 +27,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -211,25 +210,6 @@
             mGameManager.notifyGraphicsEnvironmentSetup();
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
-
-        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setBlobCacheQuotaBytes");
-        new Thread(() -> {
-            try {
-                // Perform this lookup on a thread since getCacheQuotaBytes can be slow
-                // and we don't need the answer right away. The result is consumed in
-                // checkMultifileCacheSize in egl_cache_multifile.cpp. If the value isn't
-                // ready, a default is used. The results will likely be ready by the time
-                // an app starts using the blobcache.
-                final StorageManager sm = context.getSystemService(StorageManager.class);
-                final long cacheBytes = sm.getCacheQuotaBytes(appInfoWithMetaData.storageUuid);
-                final long scaledCacheBytes =
-                        getScaledCacheQuotaBytes(packageName, cacheBytes, appInfoWithMetaData);
-                setBlobCacheQuotaBytes(scaledCacheBytes);
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to look up getCacheQuotaBytes for package: " + packageName);
-            }
-        }).start();
-        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
     }
 
     /**
@@ -468,31 +448,6 @@
         return ai;
     }
 
-    private static long getScaledCacheQuotaBytes(String packageName, long cacheBytes,
-                                                 ApplicationInfo appInfoWithMetaData) {
-        if (cacheBytes <= 0) {
-            Log.v(TAG, "cacheBytes are zero for " + packageName);
-            return 0;
-        }
-        // Limit the amount of temp storage available to OpenGL drivers to a percentage
-        // based on the app type. Games will typically need more space than applications,
-        // so give them 50%, 25% for everything else.
-        long scaledCacheBytes = 0;
-        if (((appInfoWithMetaData.flags & ApplicationInfo.FLAG_IS_GAME) != 0)
-                || appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
-            scaledCacheBytes = cacheBytes / 2;
-            Log.v(TAG, "Treating " + packageName
-                    + " as a game, setting shader cache quota to 50% ("
-                    + scaledCacheBytes + ")");
-        } else {
-            scaledCacheBytes = cacheBytes / 4;
-            Log.v(TAG, "Treating " + packageName
-                    + " as an application, setting shader cache quota to 25% ("
-                    + scaledCacheBytes + ")");
-        }
-        return scaledCacheBytes;
-    }
-
     /**
      * Return the appropriate "default" driver, unless overridden by isAngleEnabledByGameMode().
      */
@@ -1041,7 +996,4 @@
      * Then the app process is allowed to send stats to GpuStats module.
      */
     public static native void hintActivityLaunch();
-
-    private static native void setBlobCacheQuotaBytes(long cacheBytes);
-
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index efe8238..b236d66 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -224,6 +224,7 @@
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
     public static final String ACTION_USER_SETTINGS =
             "android.settings.USER_SETTINGS";
 
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index a892570..bffa660 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -79,6 +79,12 @@
     /** @hide */
     public static final int PRIORITY_CATEGORY_CONVERSATIONS = 8;
 
+    /**
+     * Total number of priority categories. Keep updated with any updates to PriorityCategory enum.
+     * @hide
+     */
+    public static final int NUM_PRIORITY_CATEGORIES = 9;
+
     /** @hide */
     @IntDef(prefix = { "VISUAL_EFFECT_" }, value = {
             VISUAL_EFFECT_FULL_SCREEN_INTENT,
@@ -107,6 +113,12 @@
     /** @hide */
     public static final int VISUAL_EFFECT_NOTIFICATION_LIST = 6;
 
+    /**
+     * Total number of visual effects. Keep updated with any updates to VisualEffect enum.
+     * @hide
+     */
+    public static final int NUM_VISUAL_EFFECTS = 7;
+
     /** @hide */
     @IntDef(prefix = { "PEOPLE_TYPE_" }, value = {
             PEOPLE_TYPE_UNSET,
@@ -202,8 +214,8 @@
 
     /** @hide */
     public ZenPolicy() {
-        mPriorityCategories = new ArrayList<>(Collections.nCopies(9, 0));
-        mVisualEffects = new ArrayList<>(Collections.nCopies(7, 0));
+        mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
+        mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
     }
 
     /**
@@ -804,8 +816,12 @@
         @Override
         public ZenPolicy createFromParcel(Parcel source) {
             ZenPolicy policy = new ZenPolicy();
-            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
-            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+            policy.mPriorityCategories = trimList(
+                    source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
+                    NUM_PRIORITY_CATEGORIES);
+            policy.mVisualEffects = trimList(
+                    source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
+                    NUM_VISUAL_EFFECTS);
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
             policy.mConversationSenders = source.readInt();
@@ -832,6 +848,15 @@
                 .toString();
     }
 
+    // Returns a list containing the first maxLength elements of the input list if the list is
+    // longer than that size. For the lists in ZenPolicy, this should not happen unless the input
+    // is corrupt.
+    private static ArrayList<Integer> trimList(ArrayList<Integer> list, int maxLength) {
+        if (list == null || list.size() <= maxLength) {
+            return list;
+        }
+        return new ArrayList<>(list.subList(0, maxLength));
+    }
 
     private String priorityCategoriesToString() {
         StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 3b082bc..787ffb7 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiThread;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 import android.window.SurfaceSyncGroup;
@@ -149,4 +150,21 @@
     default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
         return null;
     }
+
+    /**
+     * Set a crop region on all children parented to the layer represented by this
+     * AttachedSurfaceControl. This includes SurfaceView, and an example usage may
+     * be to ensure that SurfaceView with {@link android.view.SurfaceView#setZOrderOnTop}
+     * are cropped to a region not including the app bar.
+     *
+     * This cropped is expressed in terms of insets in window-space. Negative insets
+     * are considered invalid and will produce an exception. Insets of zero will produce
+     * the same result as if this function had never been called.
+     *
+     * @param insets The insets in each direction by which to bound the children
+     *               expressed in window-space.
+     * @hide
+     */
+    default void setChildBoundingInsets(@NonNull Rect insets) {
+    }
 }
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 0a2b06c..58ee59d 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -323,8 +323,7 @@
         public String toString() {
             StringBuilder sb = new StringBuilder(32);
             sb.append("TypedInsetsSize: {");
-            sb.append("windowType=").append(ViewDebug.intToString(
-                    WindowManager.LayoutParams.class, "type", windowType));
+            sb.append("windowType=").append(windowType);
             sb.append(", insetsSize=").append(insetsSize);
             sb.append("}");
             return sb.toString();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ea7a64e..52232f8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -919,6 +919,8 @@
                 }
             }
         };
+    private final Rect mChildBoundingInsets = new Rect();
+    private boolean mChildBoundingInsetsChanged = false;
 
     private String mTag = TAG;
 
@@ -2222,6 +2224,8 @@
         mTempRect.inset(mWindowAttributes.surfaceInsets.left,
                 mWindowAttributes.surfaceInsets.top,
                 mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
+        mTempRect.inset(mChildBoundingInsets.left, mChildBoundingInsets.top,
+            mChildBoundingInsets.right, mChildBoundingInsets.bottom);
         t.setWindowCrop(mBoundsLayer, mTempRect);
     }
 
@@ -2243,7 +2247,7 @@
         if (!sc.isValid()) return;
 
         if (updateBoundsLayer(t)) {
-              mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
+            applyTransactionOnDraw(t);
         }
     }
 
@@ -2329,7 +2333,6 @@
      */
     void notifyRendererOfFramePending() {
         if (mAttachInfo.mThreadedRenderer != null) {
-            mAttachInfo.mThreadedRenderer.notifyCallbackPending();
             mAttachInfo.mThreadedRenderer.notifyFramePending();
         }
     }
@@ -3447,7 +3450,8 @@
             }
         }
 
-        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
+        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated ||
+            windowAttributesChanged || mChildBoundingInsetsChanged) {
             // If the surface has been replaced, there's a chance the bounds layer is not parented
             // to the new layer. When updating bounds layer, also reparent to the main VRI
             // SurfaceControl to ensure it's correctly placed in the hierarchy.
@@ -3458,6 +3462,7 @@
             // enough. WMS doesn't want to keep around old children since they will leak when the
             // client creates new children.
             prepareSurfaces();
+            mChildBoundingInsetsChanged = false;
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -11078,7 +11083,7 @@
     @Nullable public SurfaceControl.Transaction buildReparentTransaction(
         @NonNull SurfaceControl child) {
         if (mSurfaceControl.isValid()) {
-            return new SurfaceControl.Transaction().reparent(child, mSurfaceControl);
+          return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
         }
         return null;
     }
@@ -11334,4 +11339,14 @@
         }
         mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
     }
+
+    @Override
+    public void setChildBoundingInsets(@NonNull Rect insets) {
+        if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
+            throw new IllegalArgumentException("Negative insets passed to setChildBoundingInsets.");
+        }
+        mChildBoundingInsets.set(insets);
+        mChildBoundingInsetsChanged = true;
+        scheduleTraversals();
+    }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e422732..f375ccb 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4759,10 +4759,10 @@
             }
             if (providedInsets != null) {
                 sb.append(System.lineSeparator());
-                sb.append(prefix).append("  providedInsets:");
+                sb.append(" providedInsets=");
                 for (int i = 0; i < providedInsets.length; ++i) {
-                    sb.append(System.lineSeparator());
-                    sb.append(prefix).append("    ").append(providedInsets[i]);
+                    if (i > 0) sb.append(' ');
+                    sb.append((providedInsets[i]));
                 }
             }
             if (insetsRoundedCornerFrame) {
@@ -4771,12 +4771,10 @@
             }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
-                sb.append(prefix).append("  paramsForRotation:");
+                sb.append(prefix).append("  paramsForRotation=");
                 for (int i = 0; i < paramsForRotation.length; ++i) {
-                    // Additional prefix needed for the beginning of the params of the new rotation.
-                    sb.append(System.lineSeparator()).append(prefix).append("    ");
-                    sb.append(Surface.rotationToString(i)).append("=");
-                    sb.append(paramsForRotation[i].toString(prefix + "    "));
+                    if (i > 0) sb.append(' ');
+                    sb.append(paramsForRotation[i].toString());
                 }
             }
 
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index f74d294..be9cbff 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -123,7 +123,7 @@
 
     private void receive(
             int resultCode, Bundle resultData,
-            @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+            @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
         final int callbackId = resultData.getInt(RESULT_KEY_ID);
         if (resultCode == RESULT_CODE_REGISTER) {
             int priority = resultData.getInt(RESULT_KEY_PRIORITY);
@@ -140,11 +140,11 @@
             @NonNull IOnBackInvokedCallback iCallback,
             @OnBackInvokedDispatcher.Priority int priority,
             int callbackId,
-            @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+            @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
         final ImeOnBackInvokedCallback imeCallback =
                 new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
         mImeCallbacks.add(imeCallback);
-        receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback);
+        receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority);
     }
 
     private void unregisterReceivedCallback(
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
index 65e8c64..a50fbb8 100644
--- a/core/java/com/android/internal/app/AppLocaleCollector.java
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -19,49 +19,184 @@
 import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
 import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
 
+import android.app.LocaleManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.LocaleList;
+import android.os.SystemProperties;
+import android.provider.Settings;
 import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /** The Locale data collector for per-app language. */
-class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
     private static final String TAG = AppLocaleCollector.class.getSimpleName();
     private final Context mContext;
     private final String mAppPackageName;
-    private final LocaleStore.LocaleInfo mAppCurrentLocale;
+    private LocaleStore.LocaleInfo mAppCurrentLocale;
+    private Set<LocaleStore.LocaleInfo> mAllAppActiveLocales;
+    private Set<LocaleStore.LocaleInfo> mImeLocales;
+    private static final String PROP_APP_LANGUAGE_SUGGESTION =
+            "android.app.language.suggestion.enhanced";
+    private static final boolean ENABLED = true;
 
-    AppLocaleCollector(Context context, String appPackageName) {
+    public AppLocaleCollector(Context context, String appPackageName) {
         mContext = context;
         mAppPackageName = appPackageName;
-        mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo(
-                mContext, mAppPackageName);
+    }
+
+    @VisibleForTesting
+    public LocaleStore.LocaleInfo getAppCurrentLocale() {
+        return LocaleStore.getAppActivatedLocaleInfo(mContext, mAppPackageName, true);
+    }
+
+    /**
+     * Get all applications' activated locales.
+     * @return A set which includes all applications' activated LocaleInfo.
+     */
+    @VisibleForTesting
+    public Set<LocaleStore.LocaleInfo> getAllAppActiveLocales() {
+        PackageManager pm = mContext.getPackageManager();
+        LocaleManager lm = mContext.getSystemService(LocaleManager.class);
+        HashSet<LocaleStore.LocaleInfo> result = new HashSet<>();
+        if (pm != null && lm != null) {
+            HashMap<String, LocaleStore.LocaleInfo> map = new HashMap<>();
+            for (ApplicationInfo appInfo : pm.getInstalledApplications(
+                    PackageManager.ApplicationInfoFlags.of(0))) {
+                LocaleStore.LocaleInfo localeInfo = LocaleStore.getAppActivatedLocaleInfo(
+                        mContext, appInfo.packageName, false);
+                // For the locale to be added into the suggestion area, its country could not be
+                // empty.
+                if (localeInfo != null && localeInfo.getLocale().getCountry().length() > 0) {
+                    map.put(localeInfo.getId(), localeInfo);
+                }
+            }
+            map.forEach((language, localeInfo) -> result.add(localeInfo));
+        }
+        return result;
+    }
+
+    /**
+     * Get all locales that active IME supports.
+     *
+     * @return A set which includes all LocaleInfo that active IME supports.
+     */
+    @VisibleForTesting
+    public Set<LocaleStore.LocaleInfo> getActiveImeLocales() {
+        Set<LocaleStore.LocaleInfo> activeImeLocales = null;
+        InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+        if (imm != null) {
+            InputMethodInfo activeIme = getActiveIme(imm);
+            if (activeIme != null) {
+                activeImeLocales = LocaleStore.transformImeLanguageTagToLocaleInfo(
+                        imm.getEnabledInputMethodSubtypeList(activeIme, true));
+            }
+        }
+        if (activeImeLocales == null) {
+            return Set.of();
+        } else {
+            return activeImeLocales.stream().filter(
+                    // For the locale to be added into the suggestion area, its country could not be
+                    // empty.
+                    info -> info.getLocale().getCountry().length() > 0).collect(
+                    Collectors.toSet());
+        }
+    }
+
+    private InputMethodInfo getActiveIme(InputMethodManager imm) {
+        InputMethodInfo activeIme = null;
+        List<InputMethodInfo> infoList = imm.getEnabledInputMethodList();
+        String imeId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD, mContext.getUserId());
+        for (InputMethodInfo method : infoList) {
+            if (method.getId().equals(imeId)) {
+                activeIme = method;
+            }
+        }
+        return activeIme;
+    }
+
+    /**
+     * Get the AppLocaleResult that the application supports.
+     * @return The AppLocaleResult that the application supports.
+     */
+    @VisibleForTesting
+    public AppLocaleStore.AppLocaleResult getAppSupportedLocales() {
+        return AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+    }
+
+    /**
+     * Get the locales that system supports excluding langTagsToIgnore.
+     *
+     * @param langTagsToIgnore A language set to be ignored.
+     * @param parent The parent locale.
+     * @param translatedOnly specified if is is only for translation.
+     * @return A set which includes the LocaleInfo that system supports, excluding langTagsToIgnore.
+     */
+    @VisibleForTesting
+    public Set<LocaleStore.LocaleInfo> getSystemSupportedLocale(Set<String> langTagsToIgnore,
+            LocaleStore.LocaleInfo parent, boolean translatedOnly) {
+        return LocaleStore.getLevelLocales(mContext, langTagsToIgnore, parent, translatedOnly);
+    }
+
+    /**
+     * Get the locales that system activates.
+     * @return A set which includes all the locales that system activates.
+     */
+    @VisibleForTesting
+    public List<LocaleStore.LocaleInfo> getSystemCurrentLocale() {
+        return LocaleStore.getSystemCurrentLocaleInfo();
     }
 
     @Override
     public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
         HashSet<String> langTagsToIgnore = new HashSet<>();
 
-        LocaleList systemLangList = LocaleList.getDefault();
-        for(int i = 0; i < systemLangList.size(); i++) {
-            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
-        }
-
         if (mAppCurrentLocale != null) {
             langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
         }
+
+        if (SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION, ENABLED)) {
+            // Add the locale that other App activated
+            mAllAppActiveLocales.forEach(
+                    info -> langTagsToIgnore.add(info.getLocale().toLanguageTag()));
+            // Add the locale that active IME enabled
+            mImeLocales.forEach(info -> langTagsToIgnore.add(info.getLocale().toLanguageTag()));
+        }
+
+        // Add System locales
+        LocaleList systemLangList = LocaleList.getDefault();
+        for (int i = 0; i < systemLangList.size(); i++) {
+            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+        }
         return langTagsToIgnore;
     }
 
     @Override
     public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
             boolean translatedOnly, boolean isForCountryMode) {
-        AppLocaleStore.AppLocaleResult result =
-                AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+        if (mAppCurrentLocale == null) {
+            mAppCurrentLocale = getAppCurrentLocale();
+        }
+        if (mAllAppActiveLocales == null) {
+            mAllAppActiveLocales = getAllAppActiveLocales();
+        }
+        if (mImeLocales == null) {
+            mImeLocales = getActiveImeLocales();
+        }
+        AppLocaleStore.AppLocaleResult result = getAppSupportedLocales();
         Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
         Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>();
         Set<LocaleStore.LocaleInfo> systemLocaleList;
@@ -71,11 +206,9 @@
 
         // Get system supported locale list
         if (isForCountryMode) {
-            systemLocaleList = LocaleStore.getLevelLocales(mContext,
-                    langTagsToIgnore, parent, translatedOnly);
+            systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, parent, translatedOnly);
         } else {
-            systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
-                    null /* no parent */, translatedOnly);
+            systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, null, translatedOnly);
         }
 
         // Add current app locale
@@ -84,19 +217,46 @@
         }
 
         // Add current system language into suggestion list
-        for(LocaleStore.LocaleInfo localeInfo:
-                LocaleStore.getSystemCurrentLocaleInfo()) {
-            boolean isNotCurrentLocale = mAppCurrentLocale == null
-                    || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
-            if (!isForCountryMode && isNotCurrentLocale) {
-                appLocaleList.add(localeInfo);
+        if (!isForCountryMode) {
+            boolean isCurrentLocale, isInAppOrIme;
+            for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocale()) {
+                isCurrentLocale = mAppCurrentLocale != null
+                        && localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
+                isInAppOrIme = existsInAppOrIme(localeInfo.getLocale());
+                if (!isCurrentLocale && !isInAppOrIme) {
+                    appLocaleList.add(localeInfo);
+                }
             }
         }
 
-        // Add the languages that included in system supported locale
+        // Add the languages that are included in system supported locale
+        Set<LocaleStore.LocaleInfo> suggestedSet = null;
         if (shouldShowList) {
-            appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale(
-                    systemLocaleList, result.mAppSupportedLocales));
+            appLocaleList.addAll(filterSupportedLocales(systemLocaleList,
+                    result.mAppSupportedLocales));
+            suggestedSet = getSuggestedLocales(appLocaleList);
+        }
+
+        if (!isForCountryMode && SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION,
+                ENABLED)) {
+            // Add the language that other apps activate into the suggestion list.
+            Set<LocaleStore.LocaleInfo> localeSet = filterSupportedLocales(mAllAppActiveLocales,
+                    result.mAppSupportedLocales);
+            if (suggestedSet != null) {
+                // Filter out the locale with the same language and country
+                // like zh-TW vs zh-Hant-TW.
+                localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet);
+            }
+            appLocaleList.addAll(localeSet);
+            suggestedSet.addAll(localeSet);
+
+            // Add the language that the active IME enables into the suggestion list.
+            localeSet = filterSupportedLocales(mImeLocales, result.mAppSupportedLocales);
+            if (suggestedSet != null) {
+                localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet);
+            }
+            appLocaleList.addAll(localeSet);
+            suggestedSet.addAll(localeSet);
         }
 
         // Add "system language" option
@@ -117,17 +277,55 @@
         return true;
     }
 
-    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale(
-            Set<LocaleStore.LocaleInfo> systemLocaleList,
+    private Set<LocaleStore.LocaleInfo> getSuggestedLocales(Set<LocaleStore.LocaleInfo> localeSet) {
+        return localeSet.stream().filter(localeInfo -> localeInfo.isSuggested()).collect(
+                Collectors.toSet());
+    }
+
+    private Set<LocaleStore.LocaleInfo> filterSameLanguageAndCountry(
+            Set<LocaleStore.LocaleInfo> newLocaleList,
+            Set<LocaleStore.LocaleInfo> existingLocaleList) {
+        Set<LocaleStore.LocaleInfo> result = new HashSet<>(newLocaleList.size());
+        for (LocaleStore.LocaleInfo appLocaleInfo : newLocaleList) {
+            boolean same = false;
+            Locale appLocale = appLocaleInfo.getLocale();
+            for (LocaleStore.LocaleInfo localeInfo : existingLocaleList) {
+                Locale suggested = localeInfo.getLocale();
+                if (appLocale.getLanguage().equals(suggested.getLanguage())
+                        && appLocale.getCountry().equals(suggested.getCountry())) {
+                    same = true;
+                    break;
+                }
+            }
+            if (!same) {
+                result.add(appLocaleInfo);
+            }
+        }
+        return result;
+    }
+
+    private boolean existsInAppOrIme(Locale locale) {
+        boolean existInApp = mAllAppActiveLocales.stream().anyMatch(
+                localeInfo -> localeInfo.getLocale().equals(locale));
+        if (existInApp) {
+            return true;
+        } else {
+            return mImeLocales.stream().anyMatch(
+                    localeInfo -> localeInfo.getLocale().equals(locale));
+        }
+    }
+
+    private Set<LocaleStore.LocaleInfo> filterSupportedLocales(
+            Set<LocaleStore.LocaleInfo> suggestedLocales,
             HashSet<Locale> appSupportedLocales) {
         Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
 
-        for(LocaleStore.LocaleInfo li: systemLocaleList) {
+        for (LocaleStore.LocaleInfo li : suggestedLocales) {
             if (appSupportedLocales.contains(li.getLocale())) {
                 filteredList.add(li);
             } else {
-                for(Locale l: appSupportedLocales) {
-                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+                for (Locale l : appSupportedLocales) {
+                    if (LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
                         filteredList.add(li);
                         break;
                     }
diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java
index f3a322c..a450a05 100644
--- a/core/java/com/android/internal/app/AppLocaleStore.java
+++ b/core/java/com/android/internal/app/AppLocaleStore.java
@@ -30,7 +30,10 @@
 import java.util.Locale;
 import java.util.stream.Collectors;
 
-class AppLocaleStore {
+/**
+ * A class used to access an application's supporting locales.
+ */
+public class AppLocaleStore {
     private static final String TAG = AppLocaleStore.class.getSimpleName();
 
     public static AppLocaleResult getAppSupportedLocales(
@@ -148,8 +151,11 @@
         return false;
     }
 
-    static class AppLocaleResult {
-        enum LocaleStatus {
+    /**
+     * A class used to store an application's supporting locales.
+     */
+    public static class AppLocaleResult {
+        public enum LocaleStatus {
             UNKNOWN_FAILURE,
             NO_SUPPORTED_LANGUAGE_IN_APP,
             ASSET_LOCALE_IS_EMPTY,
@@ -158,7 +164,7 @@
         }
 
         LocaleStatus mLocaleStatus;
-        HashSet<Locale> mAppSupportedLocales;
+        public HashSet<Locale> mAppSupportedLocales;
 
         public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) {
             this.mLocaleStatus = localeStatus;
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 689dec4..d2eee91 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -23,6 +23,7 @@
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -45,10 +46,11 @@
         @VisibleForTesting static final int SUGGESTION_TYPE_SIM = 1 << 0;
         @VisibleForTesting static final int SUGGESTION_TYPE_CFG = 1 << 1;
         // Only for per-app language picker
-        private static final int SUGGESTION_TYPE_CURRENT = 1 << 2;
+        @VisibleForTesting static final int SUGGESTION_TYPE_CURRENT = 1 << 2;
         // Only for per-app language picker
-        private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3;
-
+        @VisibleForTesting  static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3;
+        // Only for per-app language picker
+        @VisibleForTesting static final int SUGGESTION_TYPE_OTHER_APP_LANGUAGE = 1 << 4;
         private final Locale mLocale;
         private final Locale mParent;
         private final String mId;
@@ -259,7 +261,16 @@
         }
     }
 
-    public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) {
+    /**
+     * Get the application's activated locale.
+     *
+     * @param context UI activity's context.
+     * @param appPackageName The application's package name.
+     * @param isAppSelected True if the application is selected in the UI; false otherwise.
+     * @return A LocaleInfo with the application's activated locale.
+     */
+    public static LocaleInfo getAppActivatedLocaleInfo(Context context, String appPackageName,
+            boolean isAppSelected) {
         if (appPackageName == null) {
             return null;
         }
@@ -272,7 +283,11 @@
 
             if (locale != null) {
                 LocaleInfo localeInfo = new LocaleInfo(locale);
-                localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT;
+                if (isAppSelected) {
+                    localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT;
+                } else {
+                    localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE;
+                }
                 localeInfo.mIsTranslated = true;
                 return localeInfo;
             }
@@ -283,6 +298,24 @@
     }
 
     /**
+     * Transform IME's language tag to LocaleInfo.
+     *
+     * @param list A list which includes IME's subtype.
+     * @return A LocaleInfo set which includes IME's language tags.
+     */
+    public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
+            List<InputMethodSubtype> list) {
+        Set<LocaleInfo> imeLocales = new HashSet<>();
+        for (InputMethodSubtype subtype : list) {
+            LocaleInfo localeInfo = new LocaleInfo(subtype.getLanguageTag());
+            localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE;
+            localeInfo.mIsTranslated = true;
+            imeLocales.add(localeInfo);
+        }
+        return imeLocales;
+    }
+
+    /**
      * Returns a list of system languages with LocaleInfo
      */
     public static List<LocaleInfo> getSystemCurrentLocaleInfo() {
@@ -458,4 +491,13 @@
         }
         return result;
     }
+
+    /**
+     * API for testing.
+     */
+    @UnsupportedAppUsage
+    @VisibleForTesting
+    public static LocaleInfo fromLocale(Locale locale) {
+        return new LocaleInfo(locale);
+    }
 }
diff --git a/core/java/com/android/internal/content/InstallLocationUtils.java b/core/java/com/android/internal/content/InstallLocationUtils.java
index c456cf3..4d9c09e 100644
--- a/core/java/com/android/internal/content/InstallLocationUtils.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -31,6 +31,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
@@ -261,12 +262,12 @@
 
         // We're left with new installations with either preferring external or auto, so just pick
         // volume with most space
+        String bestCandidate = !volumePaths.isEmpty() ? volumePaths.keyAt(0) : null;
         if (volumePaths.size() == 1) {
             if (checkFitOnVolume(storageManager, volumePaths.valueAt(0), params)) {
-                return volumePaths.keyAt(0);
+                return bestCandidate;
             }
         } else {
-            String bestCandidate = null;
             long bestCandidateAvailBytes = Long.MIN_VALUE;
             for (String vol : volumePaths.keySet()) {
                 final String volumePath = volumePaths.get(vol);
@@ -289,6 +290,14 @@
 
         }
 
+        // For new installations of a predefined size, check property to let it through
+        // regardless of the actual free space.
+        if (bestCandidate != null && Integer.MAX_VALUE == params.sizeBytes
+                && SystemProperties.getBoolean("debug.pm.install_skip_size_check_for_maxint",
+                false)) {
+            return bestCandidate;
+        }
+
         throw new IOException("No special requests, but no room on allowed volumes. "
                 + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
     }
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 0df006d..65655b7 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -841,7 +841,19 @@
         return sw.toString();
     }
 
-    final public String printCurrentState(long now) {
+    /**
+     * Returns current CPU state with all the processes as a String, sorted by load
+     * in descending order.
+     */
+    public final String printCurrentState(long now) {
+        return printCurrentState(now, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns current CPU state with the top {@code maxProcessesToDump} highest load
+     * processes as a String, sorted by load in descending order.
+     */
+    public final String printCurrentState(long now, int maxProcessesToDump) {
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
         buildWorkingProcs();
@@ -883,8 +895,8 @@
         if (DEBUG) Slog.i(TAG, "totalTime " + totalTime + " over sample time "
                 + (mCurrentSampleTime-mLastSampleTime));
 
-        int N = mWorkingProcs.size();
-        for (int i=0; i<N; i++) {
+        int dumpedProcessCount = Math.min(maxProcessesToDump, mWorkingProcs.size());
+        for (int i = 0; i < dumpedProcessCount; i++) {
             Stats st = mWorkingProcs.get(i);
             printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": "  "),
                     st.pid, st.name, (int)st.rel_uptime,
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 19cb30e..75f8402 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2739,12 +2739,26 @@
 }
 
 static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
-                                                                   jint strategy, jint role) {
+                                                                   jint strategy, jint role,
+                                                                   jintArray jDeviceTypes,
+                                                                   jobjectArray jDeviceAddresses) {
+    AudioDeviceTypeAddrVector nDevices;
+    jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices);
+    if (results != NO_ERROR) {
+        return results;
+    }
+    int status = check_AudioSystem_Command(
+            AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy,
+                                                      (device_role_t)role, nDevices));
+    return (jint)status;
+}
+
+static jint android_media_AudioSystem_clearDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
+                                                                  jint strategy, jint role) {
     return (jint)
-            check_AudioSystem_Command(AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)
-                                                                                        strategy,
-                                                                                (device_role_t)
-                                                                                        role),
+            check_AudioSystem_Command(AudioSystem::clearDevicesRoleForStrategy((product_strategy_t)
+                                                                                       strategy,
+                                                                               (device_role_t)role),
                                       {NAME_NOT_FOUND});
 }
 
@@ -3341,8 +3355,10 @@
           (void *)android_media_AudioSystem_isCallScreeningModeSupported},
          {"setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
           (void *)android_media_AudioSystem_setDevicesRoleForStrategy},
-         {"removeDevicesRoleForStrategy", "(II)I",
+         {"removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
           (void *)android_media_AudioSystem_removeDevicesRoleForStrategy},
+         {"clearDevicesRoleForStrategy", "(II)I",
+          (void *)android_media_AudioSystem_clearDevicesRoleForStrategy},
          {"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
           (void *)android_media_AudioSystem_getDevicesForRoleAndStrategy},
          {"setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index f0ae488..78e2d31 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -122,10 +122,6 @@
     android::GraphicsEnv::getInstance().hintActivityLaunch();
 }
 
-void setBlobCacheQuotaBytes_native(JNIEnv* env, jobject clazz, jlong cacheBytes) {
-    android::GraphicsEnv::getInstance().setBlobCacheQuotaBytes(cacheBytes);
-}
-
 const JNINativeMethod g_methods[] = {
         {"isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native)},
         {"setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V",
@@ -147,7 +143,6 @@
         {"setDebugLayersGLES", "(Ljava/lang/String;)V",
          reinterpret_cast<void*>(setDebugLayersGLES_native)},
         {"hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native)},
-        {"setBlobCacheQuotaBytes", "(J)V", reinterpret_cast<void*>(setBlobCacheQuotaBytes_native)},
 };
 
 const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3274e85..124d9e6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -258,6 +258,7 @@
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.HAP_CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONF_CHANGED" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1072f57..1d1c02d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9401,8 +9401,34 @@
         <attr name="label" />
         <!-- The key character map file resource. -->
         <attr name="keyboardLayout" format="reference" />
-        <!-- The locales the given keyboard layout corresponds to. -->
-        <attr name="locale" format="string" />
+        <!-- The locales the given keyboard layout corresponds to. This is a list of
+             BCP-47 conformant language tags separated by the delimiter ',' or '|'.
+             Some examples of language tags are: en-US, zh-Hans-CN, el-Grek-polyton.
+             It includes information for language code, country code, variant, and script
+             code like ‘Latn’, ‘Cyrl’, etc. -->
+        <attr name="keyboardLocale" format="string"/>
+        <!-- The layout type of the given keyboardLayout.
+             NOTE: The enum to int value mapping must remain stable -->
+        <attr name="keyboardLayoutType" format="enum">
+            <!-- Qwerty-based keyboard layout. -->
+            <enum name="qwerty" value="1" />
+            <!-- Qwertz-based keyboard layout. -->
+            <enum name="qwertz" value="2" />
+            <!-- Azerty-based keyboard layout. -->
+            <enum name="azerty" value="3" />
+            <!-- Dvorak keyboard layout. -->
+            <enum name="dvorak" value="4" />
+            <!-- Colemak keyboard layout. -->
+            <enum name="colemak" value="5" />
+            <!-- Workman keyboard layout. -->
+            <enum name="workman" value="6" />
+            <!-- Turkish-Q keyboard layout. -->
+            <enum name="turkish_q" value="7" />
+            <!-- Turkish-F keyboard layout. -->
+            <enum name="turkish_f" value="8" />
+            <!-- Keyboard layout that has been enhanced with a large number of extra characters. -->
+            <enum name="extended" value="9" />
+        </attr>
         <!-- The vendor ID of the hardware the given layout corresponds to. @hide -->
         <attr name="vendorId" format="integer" />
         <!-- The product ID of the hardware the given layout corresponds to. @hide -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 44f85da..57eff9a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1027,6 +1027,9 @@
         <flag name="layoutDirection" value="0x2000" />
         <!-- The color mode of the screen has changed (color gamut or dynamic range). -->
         <flag name="colorMode" value="0x4000" />
+        <!-- The grammatical gender has changed, for example the user set the grammatical gender
+             from the UI. -->
+        <flag name="grammaticalGender" value="0x8000" />
         <!-- The font scaling factor has changed, that is the user has
              selected a new global font size. -->
         <flag name="fontScale" value="0x40000000" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 35ff7e8..92b4ba1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2701,6 +2701,10 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
+     with admin privileges and admin privileges can be granted/revoked from existing users. -->
+    <bool name="config_enableMultipleAdmins">false</bool>
+
     <!-- Whether the new Auto Selection Network UI should be shown -->
     <bool name="config_enableNewAutoSelectNetworkUI">false</bool>
 
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 2924ddf..97feaac 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -122,6 +122,8 @@
     <public name="physicalKeyboardHintLanguageTag" />
     <public name="physicalKeyboardHintLayoutType" />
     <public name="allowSharedIsolatedProcess" />
+    <public name="keyboardLocale" />
+    <public name="keyboardLayoutType" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c73f2f4..f54335a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -353,6 +353,7 @@
   <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
   <java-symbol type="bool" name="config_useFixedVolume" />
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
+  <java-symbol type="bool" name="config_enableMultipleAdmins"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="dimen" name="config_highResTaskSnapshotScale" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
index 87f91fa..9a999e4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -72,9 +72,9 @@
                     /* value= */ 94300);
     private static final ProgramSelector.Identifier RDS_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
-    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000111);
+    private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000111L);
     private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
                     /* value= */ 0x1013);
@@ -89,7 +89,7 @@
 
     private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
             IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-            Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+            Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
     private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
             /* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
     private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
@@ -216,7 +216,7 @@
     public void isPurge_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
     }
@@ -225,7 +225,7 @@
     public void isComplete_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
     }
@@ -234,7 +234,7 @@
     public void getModified_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Modified program info in chunk")
                 .that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
@@ -244,10 +244,10 @@
     public void getRemoved_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
-                .containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+                .containsExactly(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
     }
 
     @Test
@@ -276,6 +276,19 @@
     }
 
     @Test
+    public void getProgramList_forTunerAdapterWhenServiceDied_fails() throws Exception {
+        Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+        createRadioTuner();
+        doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.getProgramList(parameters));
+
+        assertWithMessage("Exception for getting program list when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void getDynamicProgramList_forTunerAdapter() throws Exception {
         createRadioTuner();
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
index 5bd018b..9399907 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -36,18 +36,18 @@
     private static final long AM_FREQUENCY = 700;
     private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
             ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
-    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_1 =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x1000011);
-    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_2 =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000112);
+    private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_1 =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000111L);
+    private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_2 =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000112L);
     private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
                     /* value= */ 0x1001);
     private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
-                    /* value= */ 94500);
+                    /* value= */ 220352);
 
     @Test
     public void getType_forIdentifier() {
@@ -80,13 +80,13 @@
     @Test
     public void equals_withDifferentTypesForIdentifiers_returnsFalse() {
         assertWithMessage("Identifier with different identifier type")
-                .that(FM_IDENTIFIER).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+                .that(FM_IDENTIFIER).isNotEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
     }
 
     @Test
     public void equals_withDifferentValuesForIdentifiers_returnsFalse() {
         assertWithMessage("Identifier with different identifier value")
-                .that(DAB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+                .that(DAB_DMB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
     }
 
     @Test
@@ -168,19 +168,19 @@
     @Test
     public void getFirstId_withIdInSelector() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
         ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
 
-        long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+        long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
 
         assertWithMessage("Value of the first DAB_SID_EXT identifier")
-                .that(firstIdValue).isEqualTo(DAB_SID_EXT_IDENTIFIER_1.getValue());
+                .that(firstIdValue).isEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1.getValue());
     }
 
     @Test
     public void getFirstId_withIdNotInSelector() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2};
         ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
 
         int idType = ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY;
@@ -195,13 +195,13 @@
     @Test
     public void getAllIds_withIdInSelector() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
         ProgramSelector.Identifier[] allIdsExpected =
-                {DAB_SID_EXT_IDENTIFIER_1, DAB_SID_EXT_IDENTIFIER_2};
+                {DAB_DMB_SID_EXT_IDENTIFIER_1, DAB_DMB_SID_EXT_IDENTIFIER_2};
         ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
 
         ProgramSelector.Identifier[] allIds =
-                selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+                selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
 
         assertWithMessage("All DAB_SID_EXT identifiers in selector")
                 .that(allIds).isEqualTo(allIdsExpected);
@@ -244,14 +244,14 @@
     @Test
     public void withSecondaryPreferred() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
         long[] vendorIdsExpected = {12345, 678};
         ProgramSelector selector = getDabSelector(secondaryIds, vendorIdsExpected);
         ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_1};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_1};
 
         ProgramSelector selectorPreferred =
-                selector.withSecondaryPreferred(DAB_SID_EXT_IDENTIFIER_1);
+                selector.withSecondaryPreferred(DAB_DMB_SID_EXT_IDENTIFIER_1);
 
         assertWithMessage("Program type")
                 .that(selectorPreferred.getProgramType()).isEqualTo(selector.getProgramType());
@@ -458,7 +458,7 @@
 
     private ProgramSelector getDabSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
             @Nullable long[] vendorIds) {
-        return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_SID_EXT_IDENTIFIER_1, secondaryIds,
+        return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1, secondaryIds,
                 vendorIds);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 03742eb..afbf8c3 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -18,7 +18,9 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -105,17 +107,17 @@
     private static final int INFO_FLAGS = 0b110001;
     private static final int SIGNAL_QUALITY = 2;
     private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000111);
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000111L);
     private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_RELATED =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000113);
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000113L);
     private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
                     /* value= */ 0x1013);
     private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
-                    /* value= */ 95500);
+                    /* value= */ 220352);
     private static final ProgramSelector DAB_SELECTOR =
             new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER,
                     new ProgramSelector.Identifier[]{
@@ -859,13 +861,13 @@
     @Test
     public void getLogicallyTunedTo_forProgramInfo() {
         assertWithMessage("Identifier logically tuned to in DAB program info")
-                .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
+                .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
     }
 
     @Test
     public void getPhysicallyTunedTo_forProgramInfo() {
         assertWithMessage("Identifier physically tuned to DAB program info")
-                .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
+                .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
     }
 
     @Test
@@ -1006,6 +1008,35 @@
     }
 
     @Test
+    public void listModules_forRadioManagerWithNullListAsInput_fails() throws Exception {
+        createRadioManager();
+
+        assertWithMessage("Status when listing module with empty list input")
+                .that(mRadioManager.listModules(null)).isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
+    public void listModules_withNullListFromService_fails() throws Exception {
+        createRadioManager();
+        when(mRadioServiceMock.listModules()).thenReturn(null);
+        List<RadioManager.ModuleProperties> modules = new ArrayList<>();
+
+        assertWithMessage("Status for listing module when getting null list from HAL client")
+                .that(mRadioManager.listModules(modules)).isEqualTo(RadioManager.STATUS_ERROR);
+    }
+
+    @Test
+    public void listModules_whenServiceDied_fails() throws Exception {
+        createRadioManager();
+        when(mRadioServiceMock.listModules()).thenThrow(new RemoteException());
+        List<RadioManager.ModuleProperties> modules = new ArrayList<>();
+
+        assertWithMessage("Status for listing module when HAL client service is dead")
+                .that(mRadioManager.listModules(modules))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void openTuner_forRadioModule() throws Exception {
         createRadioManager();
         int moduleId = 0;
@@ -1019,6 +1050,18 @@
     }
 
     @Test
+    public void openTuner_whenServiceDied_returnsNull() throws Exception {
+        createRadioManager();
+        when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()))
+                .thenThrow(new RemoteException());
+
+        RadioTuner nullTuner = mRadioManager.openTuner(/* moduleId= */ 0, FM_BAND_CONFIG,
+                /* withAudio= */ true, mCallbackMock, /* handler= */ null);
+
+        assertWithMessage("Radio tuner when service is dead").that(nullTuner).isNull();
+    }
+
+    @Test
     public void addAnnouncementListener_withListenerNotAddedBefore() throws Exception {
         createRadioManager();
         Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
@@ -1049,6 +1092,21 @@
     }
 
     @Test
+    public void addAnnouncementListener_whenServiceDied_throwException() throws Exception {
+        createRadioManager();
+        String exceptionMessage = "service is dead";
+        when(mRadioServiceMock.addAnnouncementListener(any(), any()))
+                .thenThrow(new RemoteException(exceptionMessage));
+        Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener));
+
+        assertWithMessage("Exception for adding announcement listener with dead service")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void removeAnnouncementListener_withListenerNotAddedBefore_ignores() throws Exception {
         createRadioManager();
 
@@ -1104,8 +1162,8 @@
     }
 
     private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
-        return new RadioManager.ProgramInfo(selector, DAB_FREQUENCY_IDENTIFIER,
-                DAB_SID_EXT_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
+        return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER,
+                DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
                 SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
     }
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index d851a7724..c8b4493 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -18,10 +18,13 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,6 +40,7 @@
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
 import android.os.Build;
+import android.os.RemoteException;
 
 import org.junit.After;
 import org.junit.Before;
@@ -46,7 +50,6 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -131,6 +134,24 @@
     }
 
     @Test
+    public void setConfiguration_withInvalidParameters_fails() throws Exception {
+        doThrow(new IllegalArgumentException()).when(mTunerMock).setConfiguration(any());
+
+        assertWithMessage("Status for setting configuration with invalid parameters")
+                .that(mRadioTuner.setConfiguration(TEST_BAND_CONFIG))
+                .isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
+    public void setConfiguration_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).setConfiguration(any());
+
+        assertWithMessage("Status for setting configuration when service is dead")
+                .that(mRadioTuner.setConfiguration(TEST_BAND_CONFIG))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void getConfiguration_forTunerAdapter() throws Exception {
         when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG);
         RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
@@ -144,14 +165,52 @@
     }
 
     @Test
+    public void getConfiguration_withInvalidParameters_fails() throws Exception {
+        RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[0];
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> mRadioTuner.getConfiguration(bandConfigs));
+
+        assertWithMessage("Exception for getting configuration with invalid parameters")
+                .that(thrown).hasMessageThat().contains("must be an array of length 1");
+    }
+
+    @Test
+    public void getConfiguration_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).getConfiguration();
+        RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
+
+        assertWithMessage("Status for getting configuration when service is dead")
+                .that(mRadioTuner.getConfiguration(bandConfigs))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void setMute_forTunerAdapter() {
-        int status = mRadioTuner.setMute(/* mute= */ true);
+        int status = mRadioTuner.setMute(true);
 
         assertWithMessage("Status for setting mute")
                 .that(status).isEqualTo(RadioManager.STATUS_OK);
     }
 
     @Test
+    public void setMute_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).setMuted(anyBoolean());
+
+        assertWithMessage("Status for setting muted when service is in illegal state")
+                .that(mRadioTuner.setMute(true)).isEqualTo(RadioManager.STATUS_ERROR);
+    }
+
+    @Test
+    public void setMute_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).setMuted(anyBoolean());
+
+        assertWithMessage("Status for setting muted when service is dead")
+                .that(mRadioTuner.setMute(true))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void getMute_forTunerAdapter() throws Exception {
         when(mTunerMock.isMuted()).thenReturn(true);
 
@@ -161,6 +220,14 @@
     }
 
     @Test
+    public void getMute_whenServiceDied_returnsTrue() throws Exception {
+        when(mTunerMock.isMuted()).thenThrow(new RemoteException());
+
+        assertWithMessage("Status for getting muted when service is dead")
+                .that(mRadioTuner.getMute()).isEqualTo(true);
+    }
+
+    @Test
     public void step_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
@@ -176,6 +243,24 @@
     }
 
     @Test
+    public void step_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for stepping when service is in illegal state")
+                .that(mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void step_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for stepping when service is dead")
+                .that(mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void scan_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
@@ -191,13 +276,31 @@
     }
 
     @Test
+    public void scan_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for scanning when service is in illegal state")
+                .that(mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void scan_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for scan when service is dead")
+                .that(mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void seek_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
             return RadioManager.STATUS_OK;
         }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
-        int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+        int scanStatus = mRadioTuner.seek(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
 
         verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         assertWithMessage("Status for seeking")
@@ -212,13 +315,31 @@
             return RadioManager.STATUS_OK;
         }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
-        mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
+        mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true);
 
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
                 RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
     }
 
     @Test
+    public void seek_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for seeking when service is in illegal state")
+                .that(mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void seek_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for seeking when service is dead")
+                .that(mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void tune_withChannelsForTunerAdapter_succeeds() {
         int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0);
 
@@ -228,6 +349,33 @@
     }
 
     @Test
+    public void tune_withInvalidChannel_fails() throws Exception {
+        doThrow(new IllegalArgumentException()).when(mTunerMock).tune(any());
+
+        assertWithMessage("Status for tuning when service is in illegal state")
+                .that(mRadioTuner.tune(/* channel= */ 300, /* subChannel= */ 0))
+                .isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
+    public void tune_withChannelsWhenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).tune(any());
+
+        assertWithMessage("Status for tuning when service is in illegal state")
+                .that(mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void tune_withChannelsWhenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).tune(any());
+
+        assertWithMessage("Status for tuning when service is dead")
+                .that(mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception {
         mRadioTuner.tune(FM_SELECTOR);
 
@@ -250,6 +398,17 @@
     }
 
     @Test
+    public void tune_withSelectorWhenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).tune(any());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.tune(FM_SELECTOR));
+
+        assertWithMessage("Exception for tuning when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void cancel_forTunerAdapter() throws Exception {
         mRadioTuner.tune(FM_SELECTOR);
 
@@ -259,6 +418,22 @@
     }
 
     @Test
+    public void cancel_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).cancel();
+
+        assertWithMessage("Status for canceling when service is in illegal state")
+                .that(mRadioTuner.cancel()).isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void cancel_forTunerAdapterWhenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).cancel();
+
+        assertWithMessage("Status for canceling when service is dead")
+                .that(mRadioTuner.cancel()).isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void cancelAnnouncement_forTunerAdapter() throws Exception {
         mRadioTuner.cancelAnnouncement();
 
@@ -266,6 +441,17 @@
     }
 
     @Test
+    public void cancelAnnouncement_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).cancelAnnouncement();
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.cancelAnnouncement());
+
+        assertWithMessage("Exception for canceling announcement when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() {
         RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
 
@@ -295,13 +481,24 @@
         when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected);
         int imageId = 1;
 
-        Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId);
+        Bitmap image = mRadioTuner.getMetadataImage(imageId);
 
         assertWithMessage("Image obtained from id %s", imageId)
                 .that(image).isEqualTo(bitmapExpected);
     }
 
     @Test
+    public void getMetadataImage_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.getImage(anyInt())).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.getMetadataImage(/* id= */ 1));
+
+        assertWithMessage("Exception for getting metadata image when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void startBackgroundScan_forTunerAdapter() throws Exception {
         when(mTunerMock.startBackgroundScan()).thenReturn(false);
 
@@ -312,6 +509,17 @@
     }
 
     @Test
+    public void startBackgroundScan_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.startBackgroundScan()).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.startBackgroundScan());
+
+        assertWithMessage("Exception for background scan when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void isAnalogForced_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
 
@@ -322,6 +530,19 @@
     }
 
     @Test
+    public void isAnalogForced_whenNotSupported_fails() throws Exception {
+        String errorMessage = "Analog forced switch is not supported";
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG))
+                .thenThrow(new UnsupportedOperationException(errorMessage));
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                () -> mRadioTuner.isAnalogForced());
+
+        assertWithMessage("Exception for checking analog playback switch when not supported")
+                .that(thrown).hasMessageThat().contains(errorMessage);
+    }
+
+    @Test
     public void setAnalogForced_forTunerAdapter() throws Exception {
         boolean analogForced = true;
 
@@ -331,6 +552,19 @@
     }
 
     @Test
+    public void setAnalogForced_whenNotSupported_fails() throws Exception {
+        String errorMessage = "Analog forced switch is not supported";
+        doThrow(new UnsupportedOperationException(errorMessage))
+                .when(mTunerMock).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG), anyBoolean());
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                () -> mRadioTuner.setAnalogForced(/* isForced= */ false));
+
+        assertWithMessage("Exception for setting analog playback switch when not supported")
+                .that(thrown).hasMessageThat().contains(errorMessage);
+    }
+
+    @Test
     public void isConfigFlagSupported_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING))
                 .thenReturn(true);
@@ -343,6 +577,17 @@
     }
 
     @Test
+    public void isConfigFlagSupported_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING));
+
+        assertWithMessage("Exception for checking config flag support when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void isConfigFlagSet_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING))
                 .thenReturn(true);
@@ -355,6 +600,17 @@
     }
 
     @Test
+    public void isConfigFlagSet_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_DAB_LINKING));
+
+        assertWithMessage("Exception for getting config flag when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void setConfigFlag_forTunerAdapter() throws Exception {
         boolean dabFmLinking = true;
 
@@ -364,8 +620,20 @@
     }
 
     @Test
+    public void setConfigFlag_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).setConfigFlag(anyInt(), anyBoolean());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_DAB_LINKING,
+                        /* value= */ true));
+
+        assertWithMessage("Exception for setting config flag when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void getParameters_forTunerAdapter() throws Exception {
-        List<String> parameterKeys = Arrays.asList("ParameterKeyMock");
+        List<String> parameterKeys = List.of("ParameterKeyMock");
         Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
         when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters);
 
@@ -374,6 +642,18 @@
     }
 
     @Test
+    public void getParameters_whenServiceDied_fails() throws Exception {
+        List<String> parameterKeys = List.of("ParameterKeyMock");
+        when(mTunerMock.getParameters(parameterKeys)).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.getParameters(parameterKeys));
+
+        assertWithMessage("Exception for getting parameters when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void setParameters_forTunerAdapter() throws Exception {
         Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
         when(mTunerMock.setParameters(parameters)).thenReturn(parameters);
@@ -383,6 +663,18 @@
     }
 
     @Test
+    public void setParameters_whenServiceDied_fails() throws Exception {
+        Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+        when(mTunerMock.setParameters(parameters)).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.setParameters(parameters));
+
+        assertWithMessage("Exception for setting parameters when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void isAntennaConnected_forTunerAdapter() throws Exception {
         mTunerCallback.onAntennaState(/* connected= */ false);
 
@@ -391,6 +683,15 @@
     }
 
     @Test
+    public void onError_forTunerAdapter() throws Exception {
+        int errorStatus = RadioTuner.ERROR_HARDWARE_FAILURE;
+
+        mTunerCallback.onError(errorStatus);
+
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(errorStatus);
+    }
+
+    @Test
     public void hasControl_forTunerAdapter() throws Exception {
         when(mTunerMock.isClosed()).thenReturn(true);
 
@@ -398,6 +699,14 @@
     }
 
     @Test
+    public void hasControl_whenServiceDied_returnsFalse() throws Exception {
+        when(mTunerMock.isClosed()).thenThrow(new RemoteException());
+
+        assertWithMessage("Control on tuner when service is dead")
+                .that(mRadioTuner.hasControl()).isFalse();
+    }
+
+    @Test
     public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception {
         mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG);
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index a421218..82db716 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -19,6 +19,7 @@
 import android.hardware.broadcastradio.Metadata;
 import android.hardware.broadcastradio.ProgramIdentifier;
 import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
 import android.hardware.broadcastradio.VendorKeyValue;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
@@ -41,17 +42,25 @@
                 /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
     }
 
-    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector,
+            ProgramSelector.Identifier logicallyTunedTo,
+            ProgramSelector.Identifier physicallyTunedTo, int signalQuality) {
         return new RadioManager.ProgramInfo(selector,
-                selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+                logicallyTunedTo, physicallyTunedTo, /* relatedContents= */ null,
                 /* infoFlags= */ 0, signalQuality,
                 new RadioMetadata.Builder().build(), new ArrayMap<>());
     }
 
-    static RadioManager.ProgramInfo makeProgramInfo(int programType,
-            ProgramSelector.Identifier identifier, int signalQuality) {
-        ProgramSelector selector = makeProgramSelector(programType, identifier);
-        return makeProgramInfo(selector, signalQuality);
+    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+        return makeProgramInfo(selector, selector.getPrimaryId(), selector.getPrimaryId(),
+                signalQuality);
+    }
+
+    static ProgramIdentifier makeHalIdentifier(@IdentifierType int type, long value) {
+        ProgramIdentifier halDabId = new ProgramIdentifier();
+        halDabId.type = type;
+        halDabId.value = value;
+        return halDabId;
     }
 
     static ProgramSelector makeFmSelector(long freq) {
@@ -67,44 +76,48 @@
     }
 
     static android.hardware.broadcastradio.ProgramSelector makeHalFmSelector(int freq) {
-        ProgramIdentifier halId = new ProgramIdentifier();
-        halId.type = IdentifierType.AMFM_FREQUENCY_KHZ;
-        halId.value = freq;
-
-        android.hardware.broadcastradio.ProgramSelector halSelector =
-                new android.hardware.broadcastradio.ProgramSelector();
-        halSelector.primaryId = halId;
-        halSelector.secondaryIds = new ProgramIdentifier[0];
-        return halSelector;
+        ProgramIdentifier halId = makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ, freq);
+        return makeHalSelector(halId, /* secondaryIds= */ new ProgramIdentifier[0]);
     }
 
-    static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
-        // Note that because ConversionUtils does not by design provide functions for all
-        // conversions, this function only copies fields that are set by makeProgramInfo().
-        ProgramInfo hwInfo = new ProgramInfo();
-        hwInfo.selector = ConversionUtils.programSelectorToHalProgramSelector(info.getSelector());
-        hwInfo.logicallyTunedTo =
-                ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo());
-        hwInfo.physicallyTunedTo =
-                ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo());
-        hwInfo.signalQuality = info.getSignalStrength();
-        hwInfo.relatedContent = new ProgramIdentifier[]{};
-        hwInfo.metadata = new Metadata[]{};
-        return hwInfo;
+    static android.hardware.broadcastradio.ProgramSelector makeHalSelector(
+            ProgramIdentifier primaryId, ProgramIdentifier[] secondaryIds) {
+        android.hardware.broadcastradio.ProgramSelector hwSelector =
+                new android.hardware.broadcastradio.ProgramSelector();
+        hwSelector.primaryId = primaryId;
+        hwSelector.secondaryIds = secondaryIds;
+        return hwSelector;
     }
 
     static ProgramInfo makeHalProgramInfo(
             android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
+        return makeHalProgramInfo(hwSel, hwSel.primaryId, hwSel.primaryId, hwSignalQuality);
+    }
+
+    static ProgramInfo makeHalProgramInfo(
+            android.hardware.broadcastradio.ProgramSelector hwSel,
+            ProgramIdentifier logicallyTunedTo, ProgramIdentifier physicallyTunedTo,
+            int hwSignalQuality) {
         ProgramInfo hwInfo = new ProgramInfo();
         hwInfo.selector = hwSel;
-        hwInfo.logicallyTunedTo = hwSel.primaryId;
-        hwInfo.physicallyTunedTo = hwSel.primaryId;
+        hwInfo.logicallyTunedTo = logicallyTunedTo;
+        hwInfo.physicallyTunedTo = physicallyTunedTo;
         hwInfo.signalQuality = hwSignalQuality;
         hwInfo.relatedContent = new ProgramIdentifier[]{};
         hwInfo.metadata = new Metadata[]{};
         return hwInfo;
     }
 
+    static ProgramListChunk makeProgramListChunk(boolean purge, boolean complete,
+            ProgramInfo[] modified, ProgramIdentifier[] removed) {
+        ProgramListChunk halChunk = new ProgramListChunk();
+        halChunk.purge = purge;
+        halChunk.complete = complete;
+        halChunk.modified = modified;
+        halChunk.removed = removed;
+        return halChunk;
+    }
+
     static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
         VendorKeyValue vendorKeyValue = new VendorKeyValue();
         vendorKeyValue.key = vendorKey;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index f404082..98103f6 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -173,6 +173,19 @@
     }
 
     @Test
+    public void openSession_withoutAudio_fails() throws Exception {
+        createBroadcastRadioService();
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+                        /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock,
+                        TARGET_SDK_VERSION));
+
+        assertWithMessage("Exception for opening session without audio")
+                .that(thrown).hasMessageThat().contains("not supported");
+    }
+
+    @Test
     public void binderDied_forDeathRecipient() throws Exception {
         createBroadcastRadioService();
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index a1cebb6..710c150 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -20,9 +20,13 @@
 import android.hardware.broadcastradio.AmFmRegionConfig;
 import android.hardware.broadcastradio.DabTableEntry;
 import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
 import android.hardware.broadcastradio.Properties;
 import android.hardware.broadcastradio.VendorKeyValue;
 import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.Build;
@@ -33,19 +37,20 @@
 import org.junit.Test;
 
 import java.util.Map;
+import java.util.Set;
 
 public final class ConversionUtilsTest {
 
-    private static final int FM_LOWER_LIMIT = 87500;
-    private static final int FM_UPPER_LIMIT = 108000;
+    private static final int FM_LOWER_LIMIT = 87_500;
+    private static final int FM_UPPER_LIMIT = 108_000;
     private static final int FM_SPACING = 200;
     private static final int AM_LOWER_LIMIT = 540;
-    private static final int AM_UPPER_LIMIT = 1700;
+    private static final int AM_UPPER_LIMIT = 1_700;
     private static final int AM_SPACING = 10;
     private static final String DAB_ENTRY_LABEL_1 = "5A";
-    private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+    private static final int DAB_ENTRY_FREQUENCY_1 = 174_928;
     private static final String DAB_ENTRY_LABEL_2 = "12D";
-    private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+    private static final int DAB_ENTRY_FREQUENCY_2 = 229_072;
     private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
     private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
     private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
@@ -57,6 +62,50 @@
     private static final String TEST_VERSION = "versionMock";
     private static final String TEST_SERIAL = "serialMock";
 
+    private static final int TEST_SIGNAL_QUALITY = 1;
+    private static final long TEST_DAB_DMB_SID_EXT_VALUE = 0xA000000111L;
+    private static final long TEST_DAB_ENSEMBLE_VALUE = 0x1001;
+    private static final long TEST_DAB_FREQUENCY_VALUE = 220_352;
+    private static final long TEST_FM_FREQUENCY_VALUE = 92_100;
+    private static final long TEST_VENDOR_ID_VALUE = 9_901;
+
+    private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, TEST_DAB_DMB_SID_EXT_VALUE);
+    private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, TEST_DAB_ENSEMBLE_VALUE);
+    private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, TEST_DAB_FREQUENCY_VALUE);
+    private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_FM_FREQUENCY_VALUE);
+    private static final ProgramSelector.Identifier TEST_VENDOR_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, TEST_VENDOR_ID_VALUE);
+
+    private static final ProgramIdentifier TEST_HAL_DAB_SID_EXT_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_SID_EXT, TEST_DAB_DMB_SID_EXT_VALUE);
+    private static final ProgramIdentifier TEST_HAL_DAB_ENSEMBLE_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_ENSEMBLE, TEST_DAB_ENSEMBLE_VALUE);
+    private static final ProgramIdentifier TEST_HAL_DAB_FREQUENCY_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_FREQUENCY_KHZ,
+                    TEST_DAB_FREQUENCY_VALUE);
+    private static final ProgramIdentifier TEST_HAL_FM_FREQUENCY_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ,
+                    TEST_FM_FREQUENCY_VALUE);
+    private static final ProgramIdentifier TEST_HAL_VENDOR_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.VENDOR_START,
+                    TEST_VENDOR_ID_VALUE);
+
+    private static final ProgramSelector TEST_DAB_SELECTOR = new ProgramSelector(
+            ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_EXT_ID,
+            new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID},
+            /* vendorIds= */ null);
+    private static final ProgramSelector TEST_FM_SELECTOR =
+            AidlTestUtils.makeFmSelector(TEST_FM_FREQUENCY_VALUE);
+
     private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
     private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
 
@@ -159,6 +208,246 @@
     }
 
     @Test
+    public void identifierToHalProgramIdentifier_withDabId() {
+        ProgramIdentifier halDabId =
+                ConversionUtils.identifierToHalProgramIdentifier(TEST_DAB_SID_EXT_ID);
+
+        expect.withMessage("Converted HAL DAB identifier").that(halDabId)
+                .isEqualTo(TEST_HAL_DAB_SID_EXT_ID);
+    }
+
+    @Test
+    public void identifierFromHalProgramIdentifier_withDabId() {
+        ProgramSelector.Identifier dabId =
+                ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_DAB_SID_EXT_ID);
+
+        expect.withMessage("Converted DAB identifier").that(dabId).isEqualTo(TEST_DAB_SID_EXT_ID);
+    }
+
+    @Test
+    public void programSelectorToHalProgramSelector_withValidSelector() {
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                ConversionUtils.programSelectorToHalProgramSelector(TEST_DAB_SELECTOR);
+
+        expect.withMessage("Primary identifier of converted HAL DAB selector")
+                .that(halDabSelector.primaryId).isEqualTo(TEST_HAL_DAB_SID_EXT_ID);
+        expect.withMessage("Secondary identifiers of converted HAL DAB selector")
+                .that(halDabSelector.secondaryIds).asList()
+                .containsExactly(TEST_HAL_DAB_FREQUENCY_ID, TEST_HAL_DAB_ENSEMBLE_ID);
+    }
+
+    @Test
+    public void programSelectorToHalProgramSelector_withInvalidDabSelector_returnsNull() {
+        ProgramSelector invalidDbSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                TEST_DAB_SID_EXT_ID,
+                new ProgramSelector.Identifier[0],
+                new long[0]);
+
+        android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+                ConversionUtils.programSelectorToHalProgramSelector(invalidDbSelector);
+
+        expect.withMessage("Invalid HAL DAB selector without required secondary ids")
+                .that(invalidHalDabSelector).isNull();
+    }
+
+    @Test
+    public void programSelectorFromHalProgramSelector_withValidSelector() {
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+
+        ProgramSelector dabSelector =
+                ConversionUtils.programSelectorFromHalProgramSelector(halDabSelector);
+
+        expect.withMessage("Primary identifier of converted DAB selector")
+                .that(dabSelector.getPrimaryId()).isEqualTo(TEST_DAB_SID_EXT_ID);
+        expect.withMessage("Secondary identifiers of converted DAB selector")
+                .that(dabSelector.getSecondaryIds()).asList()
+                .containsExactly(TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID);
+    }
+
+    @Test
+    public void programSelectorFromHalProgramSelector_withInvalidSelector_returnsNull() {
+        android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{});
+
+        ProgramSelector invalidDabSelector =
+                ConversionUtils.programSelectorFromHalProgramSelector(invalidHalDabSelector);
+
+        expect.withMessage("Invalid DAB selector without required secondary ids")
+                .that(invalidDabSelector).isNull();
+    }
+
+    @Test
+    public void programInfoFromHalProgramInfo_withValidProgramInfo() {
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halProgramInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+
+        RadioManager.ProgramInfo programInfo =
+                ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+
+        expect.withMessage("Primary id of selector of converted program info")
+                .that(programInfo.getSelector().getPrimaryId()).isEqualTo(TEST_DAB_SID_EXT_ID);
+        expect.withMessage("Secondary id of selector of converted program info")
+                .that(programInfo.getSelector().getSecondaryIds()).asList()
+                .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_DAB_FREQUENCY_ID);
+        expect.withMessage("Logically tuned identifier of converted program info")
+                .that(programInfo.getLogicallyTunedTo()).isEqualTo(TEST_DAB_SID_EXT_ID);
+        expect.withMessage("Physically tuned identifier of converted program info")
+                .that(programInfo.getPhysicallyTunedTo()).isEqualTo(TEST_DAB_FREQUENCY_ID);
+        expect.withMessage("Signal quality of converted program info")
+                .that(programInfo.getSignalStrength()).isEqualTo(TEST_SIGNAL_QUALITY);
+    }
+
+    @Test
+    public void programInfoFromHalProgramInfo_withInvalidDabProgramInfo() {
+        android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID,
+                new ProgramIdentifier[]{TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halProgramInfo = AidlTestUtils.makeHalProgramInfo(invalidHalDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+
+        RadioManager.ProgramInfo programInfo =
+                ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+
+        expect.withMessage("Invalid DAB program info with incorrect type of physically tuned to id")
+                .that(programInfo).isNull();
+    }
+
+    @Test
+    public void chunkFromHalProgramListChunk_withValidChunk() {
+        boolean purge = false;
+        boolean complete = true;
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+        RadioManager.ProgramInfo dabInfo =
+                ConversionUtils.programInfoFromHalProgramInfo(halDabInfo);
+        ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+                new ProgramInfo[]{halDabInfo},
+                new ProgramIdentifier[]{TEST_HAL_VENDOR_ID, TEST_HAL_FM_FREQUENCY_ID});
+
+        ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
+
+        expect.withMessage("Purged state of the converted valid program list chunk")
+                .that(chunk.isPurge()).isEqualTo(purge);
+        expect.withMessage("Completion state of the converted valid program list chunk")
+                .that(chunk.isComplete()).isEqualTo(complete);
+        expect.withMessage("Modified program info in the converted valid program list chunk")
+                .that(chunk.getModified()).containsExactly(dabInfo);
+        expect.withMessage("Removed program ides in the converted valid program list chunk")
+                .that(chunk.getRemoved()).containsExactly(TEST_VENDOR_ID, TEST_FM_FREQUENCY_ID);
+    }
+
+    @Test
+    public void chunkFromHalProgramListChunk_withInvalidModifiedProgramInfo() {
+        boolean purge = true;
+        boolean complete = false;
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+        ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+                new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_FM_FREQUENCY_ID});
+
+        ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
+
+        expect.withMessage("Purged state of the converted invalid program list chunk")
+                .that(chunk.isPurge()).isEqualTo(purge);
+        expect.withMessage("Completion state of the converted invalid program list chunk")
+                .that(chunk.isComplete()).isEqualTo(complete);
+        expect.withMessage("Modified program info in the converted invalid program list chunk")
+                .that(chunk.getModified()).isEmpty();
+        expect.withMessage("Removed program ids in the converted invalid program list chunk")
+                .that(chunk.getRemoved()).containsExactly(TEST_FM_FREQUENCY_ID);
+    }
+
+    @Test
+    public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
+        expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR)
+                .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR,
+                        Build.VERSION_CODES.TIRAMISU)).isFalse();
+    }
+
+    @Test
+    public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
+        expect.withMessage("Selector %s with required SDK version", TEST_FM_SELECTOR)
+                .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_FM_SELECTOR,
+                        Build.VERSION_CODES.TIRAMISU)).isTrue();
+    }
+
+    @Test
+    public void programInfoMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
+        RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+                TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+
+        expect.withMessage("Program info %s without required SDK version", dabProgramInfo)
+                .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(dabProgramInfo,
+                        Build.VERSION_CODES.TIRAMISU)).isFalse();
+    }
+
+    @Test
+    public void programInfoMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
+        RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+                TEST_SIGNAL_QUALITY);
+
+        expect.withMessage("Program info %s with required SDK version", fmProgramInfo)
+                .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(fmProgramInfo,
+                        Build.VERSION_CODES.TIRAMISU)).isTrue();
+    }
+
+    @Test
+    public void convertChunkToTargetSdkVersion_withLowerSdkVersion() {
+        RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+                TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+        RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+                TEST_SIGNAL_QUALITY);
+        ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
+                Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+
+        ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
+                Build.VERSION_CODES.TIRAMISU);
+
+        expect.withMessage(
+                "Purged state of the converted program list chunk with lower SDK version")
+                .that(convertedChunk.isPurge()).isEqualTo(chunk.isPurge());
+        expect.withMessage(
+                "Completion state of the converted program list chunk with lower SDK version")
+                .that(convertedChunk.isComplete()).isEqualTo(chunk.isComplete());
+        expect.withMessage(
+                "Modified program info in the converted program list chunk with lower SDK version")
+                .that(convertedChunk.getModified()).containsExactly(fmProgramInfo);
+        expect.withMessage(
+                "Removed program ids in the converted program list chunk with lower SDK version")
+                .that(convertedChunk.getRemoved())
+                .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID);
+    }
+
+    @Test
+    public void convertChunkToTargetSdkVersion_withRequiredSdkVersion() {
+        RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+                TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+        RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+                TEST_SIGNAL_QUALITY);
+        ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
+                Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+
+        ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+        expect.withMessage("Converted program list chunk with required SDK version")
+                .that(convertedChunk).isEqualTo(chunk);
+    }
+
+    @Test
     public void announcementFromHalAnnouncement_typesMatch() {
         expect.withMessage("Announcement type")
                 .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index c5c6349..d7723ac 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -26,7 +26,9 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -43,6 +45,8 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
 import android.os.Build;
+import android.os.ParcelableException;
+import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -57,7 +61,6 @@
 import org.mockito.Mock;
 import org.mockito.verification.VerificationWithTimeout;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -71,10 +74,10 @@
             timeout(/* millis= */ 200);
     private static final int SIGNAL_QUALITY = 1;
     private static final long AM_FM_FREQUENCY_SPACING = 500;
-    private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+    private static final long[] AM_FM_FREQUENCY_LIST = {97_500, 98_100, 99_100};
     private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
             new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
-                    /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
+                    /* lowerLimit= */ 87_500, /* upperLimit= */ 108_000, /* spacing= */ 100,
                     /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
                     /* ea= */ false);
     private static final RadioManager.BandConfig FM_BAND_CONFIG =
@@ -200,6 +203,17 @@
     }
 
     @Test
+    public void setConfiguration_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onConfigurationChanged(FM_BAND_CONFIG);
+    }
+
+    @Test
     public void getConfiguration() throws Exception {
         openAidlClients(/* numClients= */ 1);
         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
@@ -330,10 +344,17 @@
 
     @Test
     public void tune_withUnsupportedSelector_throwsException() throws Exception {
+        ProgramSelector.Identifier dabPrimaryId =
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                        /* value= */ 0xA000000111L);
+        ProgramSelector.Identifier[] dabSecondaryIds =  new ProgramSelector.Identifier[]{
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+                        /* value= */ 1337),
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+                        /* value= */ 225648)};
+        ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector unsupportedSelector = AidlTestUtils.makeProgramSelector(
-                ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
-                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
 
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
                 () -> mTunerSessions[0].tune(unsupportedSelector));
@@ -343,7 +364,22 @@
     }
 
     @Test
-    public void tune_forCurrentUser_doesNotTune() throws Exception {
+    public void tune_withInvalidSelector_throwsIllegalArgumentException() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector.Identifier invalidDabId = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, /* value= */ 0x1001);
+        ProgramSelector invalidSel = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                invalidDabId, new ProgramSelector.Identifier[0], new long[0]);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> mTunerSessions[0].tune(invalidSel));
+
+        assertWithMessage("Exception for tuning on DAB selector without DAB_SID_EXT primary id")
+                .that(thrown).hasMessageThat().contains("tune: INVALID_ARGUMENTS");
+    }
+
+    @Test
+    public void tune_forNonCurrentUser_doesNotTune() throws Exception {
         openAidlClients(/* numClients= */ 1);
         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
@@ -357,6 +393,21 @@
     }
 
     @Test
+    public void tune_withHalHasUnknownError_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
+                .when(mBroadcastRadioMock).tune(any());
+
+        ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+            mTunerSessions[0].tune(sel);
+        });
+
+        assertWithMessage("Exception for tuning when HAL has unknown error")
+                .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
+    }
+
+    @Test
     public void step_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[1];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -391,6 +442,35 @@
     }
 
     @Test
+    public void step_forNonCurrentUser_doesNotStep() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[1];
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onCurrentProgramInfoChanged(any());
+    }
+
+    @Test
+    public void step_withHalInInvalidState_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doThrow(new ServiceSpecificException(Result.INVALID_STATE))
+                .when(mBroadcastRadioMock).step(anyBoolean());
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
+            mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+        });
+
+        assertWithMessage("Exception for stepping when HAL is in invalid state")
+                .that(thrown).hasMessageThat().contains("INVALID_STATE");
+    }
+
+    @Test
     public void seek_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -432,11 +512,44 @@
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
 
         mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
                 .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
+    public void seek_forNonCurrentUser_doesNotSeek() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[2];
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+        RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
+                AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+                SIGNAL_QUALITY);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onCurrentProgramInfoChanged(seekUpInfo);
+    }
+
+    @Test
+    public void seek_withHalHasInternalError_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doThrow(new ServiceSpecificException(Result.INTERNAL_ERROR))
+                .when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+        ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+            mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+        });
+
+        assertWithMessage("Exception for seeking when HAL has internal error")
+                .that(thrown).hasMessageThat().contains("INTERNAL_ERROR");
+    }
+
+    @Test
     public void cancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
@@ -448,6 +561,32 @@
     }
 
     @Test
+    public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        mTunerSessions[0].tune(initialSel);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].cancel();
+
+        verify(mBroadcastRadioMock, never()).cancel();
+    }
+
+    @Test
+    public void cancel_whenHalThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        String exceptionMessage = "HAL service died.";
+        doThrow(new RemoteException(exceptionMessage)).when(mBroadcastRadioMock).cancel();
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].cancel();
+        });
+
+        assertWithMessage("Exception for canceling when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
         openAidlClients(/* numClients= */ 1);
         int imageId = IBroadcastRadio.INVALID_IMAGE;
@@ -471,6 +610,21 @@
     }
 
     @Test
+    public void getImage_whenHalThrowsException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        String exceptionMessage = "HAL service died.";
+        when(mBroadcastRadioMock.getImage(anyInt()))
+                .thenThrow(new RemoteException(exceptionMessage));
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].getImage(/* id= */ 1);
+        });
+
+        assertWithMessage("Exception for getting image when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void startBackgroundScan() throws Exception {
         openAidlClients(/* numClients= */ 1);
 
@@ -480,6 +634,16 @@
     }
 
     @Test
+    public void startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].startBackgroundScan();
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onBackgroundScanComplete();
+    }
+
+    @Test
     public void stopProgramListUpdates() throws Exception {
         openAidlClients(/* numClients= */ 1);
         ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
@@ -492,6 +656,19 @@
     }
 
     @Test
+    public void stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+                /* includeCategories= */ true, /* excludeModifications= */ false);
+        mTunerSessions[0].startProgramListUpdates(aidlFilter);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].stopProgramListUpdates();
+
+        verify(mBroadcastRadioMock, never()).stopProgramListUpdates();
+    }
+
+    @Test
     public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
         openAidlClients(/* numClients= */ 1);
         int flag = UNSUPPORTED_CONFIG_FLAG;
@@ -547,6 +724,17 @@
     }
 
     @Test
+    public void setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+
+        verify(mBroadcastRadioMock, never()).setConfigFlag(flag, /* value= */ true);
+    }
+
+    @Test
     public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
             throws Exception {
         openAidlClients(/* numClients= */ 1);
@@ -556,7 +744,7 @@
             mTunerSessions[0].isConfigFlagSet(flag);
         });
 
-        assertWithMessage("Exception for check if unsupported flag %s is set", flag)
+        assertWithMessage("Exception for checking if unsupported flag %s is set", flag)
                 .that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
     }
 
@@ -574,6 +762,20 @@
     }
 
     @Test
+    public void isConfigFlagSet_whenHalThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        doThrow(new RemoteException()).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].isConfigFlagSet(flag);
+        });
+
+        assertWithMessage("Exception for checking config flag when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains("Failed to check flag");
+    }
+
+    @Test
     public void setParameters_withMockParameters() throws Exception {
         openAidlClients(/* numClients= */ 1);
         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
@@ -586,16 +788,58 @@
     }
 
     @Test
+    public void setParameters_forNonCurrentUser_doesNotSetParameters() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+                "mockParam2", "mockValue2");
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].setParameters(parametersSet);
+
+        verify(mBroadcastRadioMock, never()).setParameters(any());
+    }
+
+    @Test
+    public void setParameters_whenHalThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+                "mockParam2", "mockValue2");
+        String exceptionMessage = "HAL service died.";
+        when(mBroadcastRadioMock.setParameters(any()))
+                .thenThrow(new RemoteException(exceptionMessage));
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].setParameters(parametersSet);
+        });
+
+        assertWithMessage("Exception for setting parameters when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void getParameters_withMockKeys() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        List<String> parameterKeys = new ArrayList<>(2);
-        parameterKeys.add("mockKey1");
-        parameterKeys.add("mockKey2");
+        List<String> parameterKeys = List.of("mockKey1", "mockKey2");
 
         mTunerSessions[0].getParameters(parameterKeys);
 
-        verify(mBroadcastRadioMock).getParameters(
-                parameterKeys.toArray(new String[0]));
+        verify(mBroadcastRadioMock).getParameters(parameterKeys.toArray(new String[0]));
+    }
+
+    @Test
+    public void getParameters_whenServiceThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        List<String> parameterKeys = List.of("mockKey1", "mockKey2");
+        String exceptionMessage = "HAL service died.";
+        when(mBroadcastRadioMock.getParameters(any()))
+                .thenThrow(new RemoteException(exceptionMessage));
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].getParameters(parameterKeys);
+        });
+
+        assertWithMessage("Exception for getting parameters when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index db16c03..6e54dcf 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -324,10 +324,17 @@
 
     @Test
     public void tune_withUnsupportedSelector_throwsException() throws Exception {
+        ProgramSelector.Identifier dabPrimaryId =
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+                        /* value= */ 0xA00111);
+        ProgramSelector.Identifier[] dabSecondaryIds =  new ProgramSelector.Identifier[]{
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+                        /* value= */ 1337),
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+                        /* value= */ 225648)};
+        ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
-                ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
-                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
 
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
                 () -> mTunerSessions[0].tune(unsupportedSelector));
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4011933..9db8805 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -16,6 +16,7 @@
 
 package android.app.activity;
 
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_EDIT;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -204,7 +205,8 @@
         try {
             // Send process level config change.
             ClientTransaction transaction = newTransaction(activityThread, null);
-            transaction.addCallback(ConfigurationChangeItem.obtain(new Configuration(newConfig)));
+            transaction.addCallback(ConfigurationChangeItem.obtain(
+                    new Configuration(newConfig), DEVICE_ID_INVALID));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -413,12 +415,14 @@
         activity.mTestLatch = new CountDownLatch(1);
 
         ClientTransaction transaction = newTransaction(activityThread, null);
-        transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape));
+        transaction.addCallback(ConfigurationChangeItem.obtain(
+                processConfigLandscape, DEVICE_ID_INVALID));
         appThread.scheduleTransaction(transaction);
 
         transaction = newTransaction(activityThread, activity.getActivityToken());
         transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
-        transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait));
+        transaction.addCallback(ConfigurationChangeItem.obtain(
+                processConfigPortrait, DEVICE_ID_INVALID));
         transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
         appThread.scheduleTransaction(transaction);
 
@@ -530,7 +534,7 @@
                     ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
 
             activityThread.updatePendingConfiguration(newAppConfig);
-            activityThread.handleConfigurationChanged(newAppConfig);
+            activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
 
             try {
                 assertEquals("Virtual display orientation must not change when process"
@@ -548,7 +552,7 @@
     private static void restoreConfig(ActivityThread thread, Configuration originalConfig) {
         thread.getConfiguration().seq = originalConfig.seq - 1;
         ResourcesManager.getInstance().getConfiguration().seq = originalConfig.seq - 1;
-        thread.handleConfigurationChanged(originalConfig);
+        thread.handleConfigurationChanged(originalConfig, DEVICE_ID_INVALID);
     }
 
     @Test
@@ -626,7 +630,7 @@
             newAppConfig.seq++;
 
             final ActivityThread activityThread = activity.getActivityThread();
-            activityThread.handleConfigurationChanged(newAppConfig);
+            activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
 
             // Verify that application config update was applied, but didn't change activity config.
             assertEquals("Activity config must not change if the process config changes",
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
index cbf167c..27ee82e 100644
--- a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
@@ -83,6 +83,13 @@
                 () -> mBackupManager.getBackupRestoreEventLogger(agent));
     }
 
+    @Test
+    public void testGetDelayedRestoreLogger_returnsRestoreLogger() {
+        BackupRestoreEventLogger logger = mBackupManager.getDelayedRestoreLogger();
+
+        assertThat(logger.getOperationType()).isEqualTo(OperationType.RESTORE);
+    }
+
     private static BackupAgent getTestAgent() {
         return new BackupAgent() {
             @Override
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 993ecf6..7f2b51d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -98,15 +98,15 @@
 
     @Test
     public void testRecycleConfigurationChangeItem() {
-        ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null);
-        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+        ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0);
+        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config());
+        ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
@@ -147,6 +147,7 @@
         persistableBundle.putInt("k", 4);
         IBinder assistToken = new Binder();
         IBinder shareableActivityToken = new Binder();
+        int deviceId = 3;
 
         Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
                 .setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
@@ -155,7 +156,7 @@
                 .setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
                 .setIsForward(true).setAssistToken(assistToken)
                 .setShareableActivityToken(shareableActivityToken)
-                .setTaskFragmentToken(new Binder()).build();
+                .setTaskFragmentToken(new Binder()).setDeviceId(deviceId).build();
 
         LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
         LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 2cd890c..0ed6a29 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -95,6 +95,7 @@
         private ActivityInfo mInfo;
         private Configuration mCurConfig;
         private Configuration mOverrideConfig;
+        private int mDeviceId;
         private String mReferrer;
         private IVoiceInteractor mVoiceInteractor;
         private int mProcState;
@@ -135,6 +136,11 @@
             return this;
         }
 
+        LaunchActivityItemBuilder setDeviceId(int deviceId) {
+            mDeviceId = deviceId;
+            return this;
+        }
+
         LaunchActivityItemBuilder setReferrer(String referrer) {
             mReferrer = referrer;
             return this;
@@ -207,7 +213,7 @@
 
         LaunchActivityItem build() {
             return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
-                    mCurConfig, mOverrideConfig, mReferrer, mVoiceInteractor,
+                    mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
                     mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
                     null /* activityClientController */, mShareableActivityToken,
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index a0ed026..f4bf1cd 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -25,53 +25,26 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.ActivityOptions;
-import android.app.ContentProviderHolder;
-import android.app.IApplicationThread;
-import android.app.IInstrumentationWatcher;
-import android.app.IUiAutomationConnection;
-import android.app.ProfilerInfo;
 import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
-import android.content.AutofillOptions;
-import android.content.ComponentName;
-import android.content.ContentCaptureOptions;
-import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ProviderInfoList;
-import android.content.pm.ServiceInfo;
-import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SharedMemory;
 import android.platform.test.annotations.Presubmit;
-import android.view.autofill.AutofillId;
-import android.view.translation.TranslationSpec;
-import android.view.translation.UiTranslationSpec;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.app.IVoiceInteractor;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
 
 /**
  * Test parcelling and unparcelling of transactions and transaction items.
@@ -97,7 +70,7 @@
     @Test
     public void testConfigurationChange() {
         // Write to parcel
-        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1 /* deviceId */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 88b2de7..3e75c7d 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -347,7 +347,7 @@
             doNothing().when(packageInfo).updateApplicationInfo(any(), any());
 
             return new ActivityClientRecord(mock(IBinder.class), Intent.makeMainActivity(component),
-                    0 /* ident */, info, new Configuration(), null /* referrer */,
+                    0 /* ident */, info, new Configuration(), 0 /*deviceId */, null /* referrer */,
                     null /* voiceInteractor */, null /* state */, null /* persistentState */,
                     null /* pendingResults */, null /* pendingNewIntents */,
                     null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
diff --git a/data/keyboards/Vendor_054c_Product_0df2.kl b/data/keyboards/Vendor_054c_Product_0df2.kl
new file mode 100644
index 0000000..a47b310
--- /dev/null
+++ b/data/keyboards/Vendor_054c_Product_0df2.kl
@@ -0,0 +1,73 @@
+# Copyright (C) 2022 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.
+
+#
+# Sony Playstation(R) DualSense Edge Controller
+#
+
+# Only use this key layout if we have HID_PLAYSTATION!
+requires_kernel_config CONFIG_HID_PLAYSTATION
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+# Square
+key 0x134   BUTTON_X
+# Cross
+key 0x130   BUTTON_A
+# Circle
+key 0x131   BUTTON_B
+# Triangle
+key 0x133   BUTTON_Y
+
+key 0x136   BUTTON_L1
+key 0x137   BUTTON_R1
+key 0x138   BUTTON_L2
+key 0x139   BUTTON_R2
+
+# L2 axis
+axis 0x02   LTRIGGER
+# R2 axis
+axis 0x05   RTRIGGER
+
+# Left Analog Stick
+axis 0x00   X
+axis 0x01   Y
+# Right Analog Stick
+axis 0x03   Z
+axis 0x04   RZ
+
+# Left stick click
+key 0x13d   BUTTON_THUMBL
+# Right stick click
+key 0x13e   BUTTON_THUMBR
+
+# Hat
+axis 0x10   HAT_X
+axis 0x11   HAT_Y
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Share / "half-sun"
+key 0x13a   BUTTON_SELECT
+# Options / three horizontal lines
+key 0x13b   BUTTON_START
+# PS key
+key 0x13c   BUTTON_MODE
+
+# SENSORs
+sensor 0x00 ACCELEROMETER X
+sensor 0x01 ACCELEROMETER Y
+sensor 0x02 ACCELEROMETER Z
+sensor 0x03 GYROSCOPE X
+sensor 0x04 GYROSCOPE Y
+sensor 0x05 GYROSCOPE Z
diff --git a/data/keyboards/Vendor_054c_Product_0df2_fallback.kl b/data/keyboards/Vendor_054c_Product_0df2_fallback.kl
new file mode 100644
index 0000000..bfebb17
--- /dev/null
+++ b/data/keyboards/Vendor_054c_Product_0df2_fallback.kl
@@ -0,0 +1,75 @@
+# Copyright (C) 2022 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.
+
+#
+# Sony Playstation(R) DualSense Edge Controller
+#
+
+# Use this if HID_PLAYSTATION is not available
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+# Square
+key 304   BUTTON_X
+# Cross
+key 305   BUTTON_A
+# Circle
+key 306   BUTTON_B
+# Triangle
+key 307   BUTTON_Y
+
+key 308   BUTTON_L1
+key 309   BUTTON_R1
+key 310   BUTTON_L2
+key 311   BUTTON_R2
+
+# L2 axis
+axis 0x03   LTRIGGER
+# R2 axis
+axis 0x04   RTRIGGER
+
+# Left Analog Stick
+axis 0x00   X
+axis 0x01   Y
+# Right Analog Stick
+axis 0x02   Z
+axis 0x05   RZ
+
+# Left stick click
+key 314   BUTTON_THUMBL
+# Right stick click
+key 315   BUTTON_THUMBR
+
+# Hat
+axis 0x10   HAT_X
+axis 0x11   HAT_Y
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Share / "half-sun"
+key 312   BUTTON_SELECT
+# Options / three horizontal lines
+key 313   BUTTON_START
+# PS key
+key 316   BUTTON_MODE
+
+# Touchpad press
+key 317 BUTTON_1
+
+# SENSORs
+sensor 0x00 ACCELEROMETER X
+sensor 0x01 ACCELEROMETER Y
+sensor 0x02 ACCELEROMETER Z
+sensor 0x03 GYROSCOPE X
+sensor 0x04 GYROSCOPE Y
+sensor 0x05 GYROSCOPE Z
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index e62ac46..2f396c0 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -668,12 +668,21 @@
     }
 
     /**
+     * Draws a mesh object to the screen.
+     *
+     * @param mesh {@link Mesh} object that will be drawn to the screen
+     * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
+     * @param paint {@link Paint} used to provide a color/shader/blend mode.
+     *
      * @hide
      */
-    public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+    public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
         if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
             throw new RuntimeException("software rendering doesn't support meshes");
         }
+        if (blendMode == null) {
+            blendMode = BlendMode.MODULATE;
+        }
         nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
                 blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
     }
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index eeff694..2ec4524 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -607,7 +607,10 @@
     }
 
     @Override
-    public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+    public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
+        if (blendMode == null) {
+            blendMode = BlendMode.MODULATE;
+        }
         nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
                 blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
     }
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index ef1e7bf..701e20c 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -161,11 +161,17 @@
          * be thrown by the decode methods when setting a non-RGB color space
          * such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
          *
-         * <p class="note">The specified color space's transfer function must be
+         * <p class="note">
+         * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+         * the specified color space's transfer function must be
          * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
          * <code>IllegalArgumentException</code> will be thrown by the decode methods
          * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
-         * specified color space returns null.</p>
+         * specified color space returns null.
+         *
+         * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+         * non ICC parametric curve transfer function is allowed.
+         * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.</p>
          *
          * <p>After decode, the bitmap's color space is stored in
          * {@link #outColorSpace}.</p>
@@ -458,7 +464,11 @@
                     throw new IllegalArgumentException("The destination color space must use the " +
                             "RGB color model");
                 }
-                if (((ColorSpace.Rgb) opts.inPreferredColorSpace).getTransferParameters() == null) {
+                if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG))
+                        && !opts.inPreferredColorSpace.equals(
+                            ColorSpace.get(ColorSpace.Named.BT2020_PQ))
+                        && ((ColorSpace.Rgb) opts.inPreferredColorSpace)
+                            .getTransferParameters() == null) {
                     throw new IllegalArgumentException("The destination color space must use an " +
                             "ICC parametric transfer function");
                 }
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 31df474..2427dec 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,8 @@
 
     private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
     private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+    private static final float[] BT2020_PRIMARIES =
+            { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f };
     /**
      * A gray color space does not have meaningful primaries, so we use this arbitrary set.
      */
@@ -208,6 +210,12 @@
 
     private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
             new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+    private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
+            new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
+                0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+    private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
+            new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+                2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
 
     // See static initialization block next to #get(Named)
     private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -703,7 +711,29 @@
          *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
          * </table>
          */
-        CIE_LAB
+        CIE_LAB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+         * Hybrid Log Gamma encoding.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Hybrid Log Gamma encoding</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         */
+        BT2020_HLG,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+         * Perceptual Quantizer encoding.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Perceptual Quantizer encoding</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         */
+        BT2020_PQ
         // Update the initialization block next to #get(Named) when adding new values
     }
 
@@ -1534,7 +1564,7 @@
         sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
         sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
                 "Rec. ITU-R BT.2020-1",
-                new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
+                BT2020_PRIMARIES,
                 ILLUMINANT_D65,
                 null,
                 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
@@ -1616,6 +1646,70 @@
                 "Generic L*a*b*",
                 Named.CIE_LAB.ordinal()
         );
+        sNamedColorSpaces[Named.BT2020_HLG.ordinal()] = new ColorSpace.Rgb(
+                "Hybrid Log Gamma encoding",
+                BT2020_PRIMARIES,
+                ILLUMINANT_D65,
+                null,
+                x -> transferHLGOETF(x),
+                x -> transferHLGEOTF(x),
+                0.0f, 1.0f,
+                BT2020_HLG_TRANSFER_PARAMETERS,
+                Named.BT2020_HLG.ordinal()
+        );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_HLG, Named.BT2020_HLG.ordinal());
+        sNamedColorSpaces[Named.BT2020_PQ.ordinal()] = new ColorSpace.Rgb(
+                "Perceptual Quantizer encoding",
+                BT2020_PRIMARIES,
+                ILLUMINANT_D65,
+                null,
+                x -> transferST2048OETF(x),
+                x -> transferST2048EOTF(x),
+                0.0f, 1.0f,
+                BT2020_PQ_TRANSFER_PARAMETERS,
+                Named.BT2020_PQ.ordinal()
+        );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal());
+    }
+
+    private static double transferHLGOETF(double x) {
+        double a = 0.17883277;
+        double b = 0.28466892;
+        double c = 0.55991073;
+        double r = 0.5;
+        return x > 1.0 ? a * Math.log(x - b) + c : r * Math.sqrt(x);
+    }
+
+    private static double transferHLGEOTF(double x) {
+        double a = 0.17883277;
+        double b = 0.28466892;
+        double c = 0.55991073;
+        double r = 0.5;
+        return x <= 0.5 ? (x * x) / (r * r) : Math.exp((x - c) / a + b);
+    }
+
+    private static double transferST2048OETF(double x) {
+        double m1 = (2610.0 / 4096.0) / 4.0;
+        double m2 = (2523.0 / 4096.0) * 128.0;
+        double c1 = (3424.0 / 4096.0);
+        double c2 = (2413.0 / 4096.0) * 32.0;
+        double c3 = (2392.0 / 4096.0) * 32.0;
+
+        double tmp = Math.pow(x, m1);
+        tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+        return Math.pow(tmp, m2);
+    }
+
+    private static double transferST2048EOTF(double x) {
+        double m1 = (2610.0 / 4096.0) / 4.0;
+        double m2 = (2523.0 / 4096.0) * 128.0;
+        double c1 = (3424.0 / 4096.0);
+        double c2 = (2413.0 / 4096.0) * 32.0;
+        double c3 = (2392.0 / 4096.0) * 32.0;
+
+        double tmp = Math.pow(Math.min(Math.max(x, 0.0), 1.0), 1.0 / m2);
+        tmp = Math.max(tmp - c1, 0.0) / (c2 - c3 * tmp);
+        return Math.pow(tmp, 1.0 / m1);
     }
 
     // Reciprocal piecewise gamma response
@@ -2197,6 +2291,58 @@
             /** Variable \(g\) in the equation of the EOTF described above. */
             public final double g;
 
+            private TransferParameters(double a, double b, double c, double d, double e,
+                    double f, double g, boolean nonCurveTransferParameters) {
+                // nonCurveTransferParameters correspondes to a "special" transfer function
+                if (!nonCurveTransferParameters) {
+                    if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)
+                            || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f)
+                            || Double.isNaN(g)) {
+                        throw new IllegalArgumentException("Parameters cannot be NaN");
+                    }
+
+                    // Next representable float after 1.0
+                    // We use doubles here but the representation inside our native code
+                    // is often floats
+                    if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
+                        throw new IllegalArgumentException(
+                            "Parameter d must be in the range [0..1], " + "was " + d);
+                    }
+
+                    if (d == 0.0 && (a == 0.0 || g == 0.0)) {
+                        throw new IllegalArgumentException(
+                            "Parameter a or g is zero, the transfer function is constant");
+                    }
+
+                    if (d >= 1.0 && c == 0.0) {
+                        throw new IllegalArgumentException(
+                            "Parameter c is zero, the transfer function is constant");
+                    }
+
+                    if ((a == 0.0 || g == 0.0) && c == 0.0) {
+                        throw new IllegalArgumentException("Parameter a or g is zero,"
+                            + " and c is zero, the transfer function is constant");
+                    }
+
+                    if (c < 0.0) {
+                        throw new IllegalArgumentException(
+                            "The transfer function must be increasing");
+                    }
+
+                    if (a < 0.0 || g < 0.0) {
+                        throw new IllegalArgumentException(
+                            "The transfer function must be positive or increasing");
+                    }
+                }
+                this.a = a;
+                this.b = b;
+                this.c = c;
+                this.d = d;
+                this.e = e;
+                this.f = f;
+                this.g = g;
+            }
+
             /**
              * <p>Defines the parameters for the ICC parametric curve type 3, as
              * defined in ICC.1:2004-10, section 10.15.</p>
@@ -2219,7 +2365,7 @@
              * @throws IllegalArgumentException If the parameters form an invalid transfer function
              */
             public TransferParameters(double a, double b, double c, double d, double g) {
-                this(a, b, c, d, 0.0, 0.0, g);
+                this(a, b, c, d, 0.0, 0.0, g, false);
             }
 
             /**
@@ -2238,51 +2384,7 @@
              */
             public TransferParameters(double a, double b, double c, double d, double e,
                     double f, double g) {
-
-                if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
-                        Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
-                        Double.isNaN(g)) {
-                    throw new IllegalArgumentException("Parameters cannot be NaN");
-                }
-
-                // Next representable float after 1.0
-                // We use doubles here but the representation inside our native code is often floats
-                if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
-                    throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
-                            "was " + d);
-                }
-
-                if (d == 0.0 && (a == 0.0 || g == 0.0)) {
-                    throw new IllegalArgumentException(
-                            "Parameter a or g is zero, the transfer function is constant");
-                }
-
-                if (d >= 1.0 && c == 0.0) {
-                    throw new IllegalArgumentException(
-                            "Parameter c is zero, the transfer function is constant");
-                }
-
-                if ((a == 0.0 || g == 0.0) && c == 0.0) {
-                    throw new IllegalArgumentException("Parameter a or g is zero," +
-                            " and c is zero, the transfer function is constant");
-                }
-
-                if (c < 0.0) {
-                    throw new IllegalArgumentException("The transfer function must be increasing");
-                }
-
-                if (a < 0.0 || g < 0.0) {
-                    throw new IllegalArgumentException("The transfer function must be " +
-                            "positive or increasing");
-                }
-
-                this.a = a;
-                this.b = b;
-                this.c = c;
-                this.d = d;
-                this.e = e;
-                this.f = f;
-                this.g = g;
+                this(a, b, c, d, e, f, g, false);
             }
 
             @SuppressWarnings("SimplifiableIfStatement")
@@ -2357,6 +2459,36 @@
         private static native long nativeCreate(float a, float b, float c, float d,
                 float e, float f, float g, float[] xyz);
 
+        private static DoubleUnaryOperator generateOETF(TransferParameters function) {
+            boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+                    || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+            if (isNonCurveTransferParameters) {
+                return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGOETF(x)
+                    : x -> transferST2048OETF(x);
+            } else {
+                return function.e == 0.0 && function.f == 0.0
+                    ? x -> rcpResponse(x, function.a, function.b,
+                    function.c, function.d, function.g)
+                    : x -> rcpResponse(x, function.a, function.b, function.c,
+                        function.d, function.e, function.f, function.g);
+            }
+        }
+
+        private static DoubleUnaryOperator generateEOTF(TransferParameters function) {
+            boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+                    || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+            if (isNonCurveTransferParameters) {
+                return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGEOTF(x)
+                    : x -> transferST2048EOTF(x);
+            } else {
+                return function.e == 0.0 && function.f == 0.0
+                    ? x -> response(x, function.a, function.b,
+                    function.c, function.d, function.g)
+                    : x -> response(x, function.a, function.b, function.c,
+                        function.d, function.e, function.f, function.g);
+            }
+        }
+
         /**
          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
          * The transform matrix must convert from the RGB space to the profile connection
@@ -2553,16 +2685,8 @@
                 @NonNull TransferParameters function,
                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
             this(name, primaries, whitePoint, transform,
-                    function.e == 0.0 && function.f == 0.0 ?
-                            x -> rcpResponse(x, function.a, function.b,
-                                    function.c, function.d, function.g) :
-                            x -> rcpResponse(x, function.a, function.b, function.c,
-                                    function.d, function.e, function.f, function.g),
-                    function.e == 0.0 && function.f == 0.0 ?
-                            x -> response(x, function.a, function.b,
-                                    function.c, function.d, function.g) :
-                            x -> response(x, function.a, function.b, function.c,
-                                    function.d, function.e, function.f, function.g),
+                    generateOETF(function),
+                    generateEOTF(function),
                     0.0f, 1.0f, function, id);
         }
 
@@ -3063,7 +3187,12 @@
          */
         @Nullable
         public TransferParameters getTransferParameters() {
-            return mTransferParameters;
+            if (mTransferParameters != null
+                    && !mTransferParameters.equals(BT2020_PQ_TRANSFER_PARAMETERS)
+                    && !mTransferParameters.equals(BT2020_HLG_TRANSFER_PARAMETERS)) {
+                return mTransferParameters;
+            }
+            return null;
         }
 
         @Override
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 1f693166..e186386 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -16,6 +16,9 @@
 
 package android.graphics;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
 import libcore.util.NativeAllocationRegistry;
 
 import java.nio.Buffer;
@@ -25,8 +28,8 @@
  * Class representing a mesh object.
  *
  * This class generates Mesh objects via the
- * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and
- * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
+ * {@link #make(MeshSpecification, int, Buffer, int, Rect)} and
+ * {@link #makeIndexed(MeshSpecification, int, Buffer, int, ShortBuffer, Rect)} methods,
  * where a {@link MeshSpecification} is required along with various attributes for
  * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
  * for the mesh. Once generated, a mesh object can be drawn through
@@ -39,9 +42,20 @@
     private boolean mIsIndexed;
 
     /**
-     * Enum to determine how the mesh is represented.
+     * Determines how the mesh is represented and will be drawn.
      */
-    public enum Mode {Triangles, TriangleStrip}
+    @IntDef({TRIANGLES, TRIANGLE_STRIP})
+    private @interface Mode {}
+
+    /**
+     * The mesh will be drawn with triangles without utilizing shared vertices.
+     */
+    public static final int TRIANGLES = 0;
+
+    /**
+     * The mesh will be drawn with triangles utilizing shared vertices.
+     */
+    public static final int TRIANGLE_STRIP = 1;
 
     private static class MeshHolder {
         public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
@@ -53,7 +67,8 @@
      * Generates a {@link Mesh} object.
      *
      * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
-     * @param mode         {@link Mode} enum
+     * @param mode         Determines what mode to draw the mesh in. Must be one of
+     *                     {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
      * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
      *                     for all attributes provided within the meshSpec for every vertex. That
      *                     is, a vertex buffer should be (attributes size * number of vertices) in
@@ -63,9 +78,13 @@
      * @param bounds       bounds of the mesh object.
      * @return a new Mesh object.
      */
-    public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
-            int vertexCount, Rect bounds) {
-        long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+    @NonNull
+    public static Mesh make(@NonNull MeshSpecification meshSpec, @Mode int mode,
+            @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) {
+        if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+            throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+        }
+        long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
                 vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
                 bounds.top, bounds.right, bounds.bottom);
         if (nativeMesh == 0) {
@@ -78,7 +97,8 @@
      * Generates a {@link Mesh} object.
      *
      * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
-     * @param mode         {@link Mode} enum
+     * @param mode         Determines what mode to draw the mesh in. Must be one of
+     *                     {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
      * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
      *                     for all attributes provided within the meshSpec for every vertex. That
      *                     is, a vertex buffer should be (attributes size * number of vertices) in
@@ -92,9 +112,14 @@
      * @param bounds       bounds of the mesh object.
      * @return a new Mesh object.
      */
-    public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
-            int vertexCount, ShortBuffer indexBuffer, Rect bounds) {
-        long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+    @NonNull
+    public static Mesh makeIndexed(@NonNull MeshSpecification meshSpec, @Mode int mode,
+            @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer,
+            @NonNull Rect bounds) {
+        if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+            throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+        }
+        long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
                 vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
                 indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
                 bounds.top, bounds.right, bounds.bottom);
@@ -114,7 +139,7 @@
      * @param color       the provided sRGB color will be converted into the shader program's output
      *                    colorspace and be available as a vec4 uniform in the program.
      */
-    public void setColorUniform(String uniformName, int color) {
+    public void setColorUniform(@NonNull String uniformName, int color) {
         setUniform(uniformName, Color.valueOf(color).getComponents(), true);
     }
 
@@ -128,7 +153,7 @@
      * @param color       the provided sRGB color will be converted into the shader program's output
      *                    colorspace and be available as a vec4 uniform in the program.
      */
-    public void setColorUniform(String uniformName, long color) {
+    public void setColorUniform(@NonNull String uniformName, long color) {
         Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
         setUniform(uniformName, exSRGB.getComponents(), true);
     }
@@ -143,7 +168,7 @@
      * @param color       the provided sRGB color will be converted into the shader program's output
      *                    colorspace and will be made available as a vec4 uniform in the program.
      */
-    public void setColorUniform(String uniformName, Color color) {
+    public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
         if (color == null) {
             throw new NullPointerException("The color parameter must not be null");
         }
@@ -160,7 +185,7 @@
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param value       float value corresponding to the float uniform with the given name.
      */
-    public void setFloatUniform(String uniformName, float value) {
+    public void setFloatUniform(@NonNull String uniformName, float value) {
         setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
     }
 
@@ -173,7 +198,7 @@
      * @param value1      first float value corresponding to the float uniform with the given name.
      * @param value2      second float value corresponding to the float uniform with the given name.
      */
-    public void setFloatUniform(String uniformName, float value1, float value2) {
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
         setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
     }
 
@@ -188,7 +213,8 @@
      * @param value3      third float value corresponding to the float unifiform with the given
      *                    name.
      */
-    public void setFloatUniform(String uniformName, float value1, float value2, float value3) {
+    public void setFloatUniform(
+            @NonNull String uniformName, float value1, float value2, float value3) {
         setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
     }
 
@@ -204,7 +230,7 @@
      * @param value4      fourth float value corresponding to the float uniform with the given name.
      */
     public void setFloatUniform(
-            String uniformName, float value1, float value2, float value3, float value4) {
+            @NonNull String uniformName, float value1, float value2, float value3, float value4) {
         setFloatUniform(uniformName, value1, value2, value3, value4, 4);
     }
 
@@ -217,7 +243,7 @@
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param values      float value corresponding to the vec4 float uniform with the given name.
      */
-    public void setFloatUniform(String uniformName, float[] values) {
+    public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
         setUniform(uniformName, values, false);
     }
 
@@ -249,7 +275,7 @@
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param value       value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value) {
+    public void setIntUniform(@NonNull String uniformName, int value) {
         setIntUniform(uniformName, value, 0, 0, 0, 1);
     }
 
@@ -262,7 +288,7 @@
      * @param value1      first value corresponding to the int uniform with the given name.
      * @param value2      second value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value1, int value2) {
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
         setIntUniform(uniformName, value1, value2, 0, 0, 2);
     }
 
@@ -276,7 +302,7 @@
      * @param value2      second value corresponding to the int uniform with the given name.
      * @param value3      third value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
         setIntUniform(uniformName, value1, value2, value3, 0, 3);
     }
 
@@ -291,7 +317,8 @@
      * @param value3      third value corresponding to the int uniform with the given name.
      * @param value4      fourth value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) {
+    public void setIntUniform(
+            @NonNull String uniformName, int value1, int value2, int value3, int value4) {
         setIntUniform(uniformName, value1, value2, value3, value4, 4);
     }
 
@@ -304,7 +331,7 @@
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param values      int values corresponding to the vec4 int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int[] values) {
+    public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
         if (uniformName == null) {
             throw new NullPointerException("The uniformName parameter must not be null");
         }
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index dd8fb7a..6ef3596 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -17,15 +17,18 @@
 package android.graphics;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.util.List;
+
 /**
  * Class responsible for holding specifications for {@link Mesh} creations. This class
  * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
  * the mesh are supplied, including attributes, vertex stride, varyings, and
  * vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an {@link AlphaType}.
+ * {@link ColorSpace} as well as an alpha type.
  *
  * Note that there are several limitations on various mesh specifications:
  * 1. The max amount of attributes allowed is 8.
@@ -42,12 +45,11 @@
     long mNativeMeshSpec;
 
     /**
-     * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+     * Constants for {@link #make(List, int, List, String, String)}
      * to determine alpha type. Describes how to interpret the alpha component of a pixel.
      */
     @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
-    public @interface AlphaType {
-    }
+    private @interface AlphaType {}
 
     /**
      * uninitialized.
@@ -73,8 +75,7 @@
      * Constants for {@link Attribute} and {@link Varying} for determining the data type.
      */
     @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
-    public @interface Type {
-    }
+    private @interface Type {}
 
     /**
      * Represents one float. Its equivalent shader type is float.
@@ -118,7 +119,7 @@
         private int mOffset;
         private String mName;
 
-        public Attribute(@Type int type, int offset, String name) {
+        public Attribute(@Type int type, int offset, @NonNull String name) {
             mType = type;
             mOffset = offset;
             mName = name;
@@ -134,7 +135,7 @@
         private int mType;
         private String mName;
 
-        public Varying(@Type int type, String name) {
+        public Varying(@Type int type, @NonNull String name) {
             mType = type;
             mName = name;
         }
@@ -162,10 +163,13 @@
      * @param fragmentShader fragment shader to be supplied to the mesh.
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
-    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
-            Varying[] varyings, String vertexShader, String fragmentShader) {
-        long nativeMeshSpec =
-                nativeMake(attributes, vertexStride, varyings, vertexShader, fragmentShader);
+    @NonNull
+    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+            @NonNull String fragmentShader) {
+        long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]),
+                vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+                fragmentShader);
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
         }
@@ -189,9 +193,12 @@
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
-    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
-            Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace) {
-        long nativeMeshSpec = nativeMakeWithCS(attributes, vertexStride, varyings, vertexShader,
+    @NonNull
+    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+            @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) {
+        long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]),
+                vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
                 fragmentShader, colorSpace.getNativeInstance());
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
@@ -215,14 +222,22 @@
      * @param fragmentShader fragment shader to be supplied to the mesh.
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @param alphaType      Describes how to interpret the alpha component for a pixel. Must be
-     *                       one of {@link AlphaType} values.
+     *                       one of
+     *                       {@link MeshSpecification#UNKNOWN},
+     *                       {@link MeshSpecification#OPAQUE},
+     *                       {@link MeshSpecification#PREMUL}, or
+     *                       {@link MeshSpecification#UNPREMULT}
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
-    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
-            Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace,
+    @NonNull
+    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+            @NonNull String fragmentShader, @NonNull ColorSpace colorSpace,
             @AlphaType int alphaType) {
-        long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
-                fragmentShader, colorSpace.getNativeInstance(), alphaType);
+        long nativeMeshSpec =
+                nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]),
+                        vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+                        fragmentShader, colorSpace.getNativeInstance(), alphaType);
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
         }
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 23db233..774f6c6 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -115,4 +115,10 @@
     <!-- Components support to launch multiple instances into split-screen -->
     <string-array name="config_appsSupportMultiInstancesSplit">
     </string-array>
+
+    <!-- Whether the extended restart dialog is enabled -->
+    <bool name="config_letterboxIsRestartDialogEnabled">false</bool>
+
+    <!-- Whether the additional education about reachability is enabled -->
+    <bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
new file mode 100644
index 0000000..4f33a71
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 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.compatui;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Configuration flags for the CompatUX implementation
+ */
+@WMSingleton
+public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
+
+    static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
+
+    static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
+            "enable_letterbox_reachability_education";
+
+    // Whether the extended restart dialog is enabled
+    private boolean mIsRestartDialogEnabled;
+
+    // Whether the additional education about reachability is enabled
+    private boolean mIsReachabilityEducationEnabled;
+
+    // Whether the extended restart dialog is enabled
+    private boolean mIsRestartDialogOverrideEnabled;
+
+    // Whether the additional education about reachability is enabled
+    private boolean mIsReachabilityEducationOverrideEnabled;
+
+    // Whether the extended restart dialog is allowed from backend
+    private boolean mIsLetterboxRestartDialogAllowed;
+
+    // Whether the additional education about reachability is allowed from backend
+    private boolean mIsLetterboxReachabilityEducationAllowed;
+
+    @Inject
+    public CompatUIConfiguration(Context context, @ShellMainThread ShellExecutor mainExecutor) {
+        mIsRestartDialogEnabled = context.getResources().getBoolean(
+                R.bool.config_letterboxIsRestartDialogEnabled);
+        mIsReachabilityEducationEnabled = context.getResources().getBoolean(
+                R.bool.config_letterboxIsReachabilityEducationEnabled);
+        mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false);
+        mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
+                false);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
+                this);
+    }
+
+    /**
+     * @return {@value true} if the restart dialog is enabled.
+     */
+    boolean isRestartDialogEnabled() {
+        return mIsRestartDialogOverrideEnabled || (mIsRestartDialogEnabled
+                && mIsLetterboxRestartDialogAllowed);
+    }
+
+    /**
+     * Enables/Disables the restart education dialog
+     */
+    void setIsRestartDialogOverrideEnabled(boolean enabled) {
+        mIsRestartDialogOverrideEnabled = enabled;
+    }
+
+    /**
+     * @return {@value true} if the reachability education is enabled.
+     */
+    boolean isReachabilityEducationEnabled() {
+        return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
+                && mIsLetterboxReachabilityEducationAllowed);
+    }
+
+    /**
+     * Enables/Disables the reachability education
+     */
+    void setIsReachabilityEducationOverrideEnabled(boolean enabled) {
+        mIsReachabilityEducationOverrideEnabled = enabled;
+    }
+
+    @Override
+    public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+        // TODO(b/263349751): Update flag and default value to true
+        if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
+            mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
+                    false);
+        }
+        if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) {
+            mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                    KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java
new file mode 100644
index 0000000..4fb18e2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 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.compatui;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles the shell commands for the CompatUX.
+ *
+ * <p> Use with {@code adb shell dumpsys activity service SystemUIService WMShell compatui
+ * &lt;command&gt;}.
+ */
+@WMSingleton
+public final class CompatUIShellCommandHandler implements
+        ShellCommandHandler.ShellCommandActionHandler {
+
+    private final CompatUIConfiguration mCompatUIConfiguration;
+    private final ShellCommandHandler mShellCommandHandler;
+
+    @Inject
+    public CompatUIShellCommandHandler(ShellCommandHandler shellCommandHandler,
+            CompatUIConfiguration compatUIConfiguration) {
+        mShellCommandHandler = shellCommandHandler;
+        mCompatUIConfiguration = compatUIConfiguration;
+    }
+
+    void onInit() {
+        mShellCommandHandler.addCommandCallback("compatui", this, this);
+    }
+
+    @Override
+    public boolean onShellCommand(String[] args, PrintWriter pw) {
+        if (args.length != 2) {
+            pw.println("Invalid command: " + args[0]);
+            return false;
+        }
+        switch (args[0]) {
+            case "restartDialogEnabled":
+                return invokeOrError(args[1], pw,
+                        mCompatUIConfiguration::setIsRestartDialogOverrideEnabled);
+            case "reachabilityEducationEnabled":
+                return invokeOrError(args[1], pw,
+                        mCompatUIConfiguration::setIsReachabilityEducationOverrideEnabled);
+            default:
+                pw.println("Invalid command: " + args[0]);
+                return false;
+        }
+    }
+
+    @Override
+    public void printShellCommandHelp(PrintWriter pw, String prefix) {
+        pw.println(prefix + "restartDialogEnabled [0|false|1|true]");
+        pw.println(prefix + "  Enable/Disable the restart education dialog for Size Compat Mode");
+        pw.println(prefix + "reachabilityEducationEnabled [0|false|1|true]");
+        pw.println(prefix
+                + "  Enable/Disable the restart education dialog for letterbox reachability");
+        pw.println(prefix + "  Disable the restart education dialog for letterbox reachability");
+    }
+
+    private static boolean invokeOrError(String input, PrintWriter pw,
+            Consumer<Boolean> setter) {
+        Boolean asBoolean = strToBoolean(input);
+        if (asBoolean == null) {
+            pw.println("Error: expected true, 1, false, 0.");
+            return false;
+        }
+        setter.accept(asBoolean);
+        return true;
+    }
+
+    // Converts a String to boolean if possible or it returns null otherwise
+    private static Boolean strToBoolean(String str) {
+        switch(str) {
+            case "1":
+            case "true":
+                return true;
+            case "0":
+            case "false":
+                return false;
+        }
+        return null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
similarity index 85%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
index 3061eab..7475fea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
 
 import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
@@ -38,10 +38,15 @@
 import com.android.internal.policy.TransitionAnimation;
 
 /**
- * Controls the enter/exit animations of the letterbox education.
+ * Controls the enter/exit a dialog.
+ *
+ * @param <T> The {@link DialogContainerSupplier} to use
  */
-class LetterboxEduAnimationController {
-    private static final String TAG = "LetterboxEduAnimation";
+public class DialogAnimationController<T extends DialogContainerSupplier> {
+
+    // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
+    // 204 is simply 255 * 0.8.
+    static final int BACKGROUND_DIM_ALPHA = 204;
 
     // If shell transitions are enabled, startEnterAnimation will be called after all transitions
     // have finished, and therefore the start delay should be shorter.
@@ -49,6 +54,7 @@
 
     private final TransitionAnimation mTransitionAnimation;
     private final String mPackageName;
+    private final String mTag;
     @AnyRes
     private final int mAnimStyleResId;
 
@@ -57,23 +63,24 @@
     @Nullable
     private Animator mBackgroundDimAnimator;
 
-    LetterboxEduAnimationController(Context context) {
-        mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, TAG);
+    public DialogAnimationController(Context context, String tag) {
+        mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, tag);
         mAnimStyleResId = (new ContextThemeWrapper(context,
                 android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes(
                 com.android.internal.R.styleable.Window).getResourceId(
                 com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
         mPackageName = context.getPackageName();
+        mTag = tag;
     }
 
     /**
      * Starts both background dim fade-in animation and the dialog enter animation.
      */
-    void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+    public void startEnterAnimation(@NonNull T layout, Runnable endCallback) {
         // Cancel any previous animation if it's still running.
         cancelAnimation();
 
-        final View dialogContainer = layout.getDialogContainer();
+        final View dialogContainer = layout.getDialogContainerView();
         mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation);
         if (mDialogAnimation == null) {
             endCallback.run();
@@ -86,8 +93,8 @@
                     endCallback.run();
                 }));
 
-        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(),
-                /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA,
+        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
+                /* endAlpha= */ BACKGROUND_DIM_ALPHA,
                 mDialogAnimation.getDuration());
         mBackgroundDimAnimator.addListener(getDimAnimatorListener());
 
@@ -101,11 +108,11 @@
     /**
      * Starts both the background dim fade-out animation and the dialog exit animation.
      */
-    void startExitAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+    public void startExitAnimation(@NonNull T layout, Runnable endCallback) {
         // Cancel any previous animation if it's still running.
         cancelAnimation();
 
-        final View dialogContainer = layout.getDialogContainer();
+        final View dialogContainer = layout.getDialogContainerView();
         mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
         if (mDialogAnimation == null) {
             endCallback.run();
@@ -119,8 +126,8 @@
                     endCallback.run();
                 }));
 
-        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), /* endAlpha= */ 0,
-                mDialogAnimation.getDuration());
+        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
+                /* endAlpha= */ 0, mDialogAnimation.getDuration());
         mBackgroundDimAnimator.addListener(getDimAnimatorListener());
 
         dialogContainer.startAnimation(mDialogAnimation);
@@ -130,7 +137,7 @@
     /**
      * Cancels all animations and resets the state of the controller.
      */
-    void cancelAnimation() {
+    public void cancelAnimation() {
         if (mDialogAnimation != null) {
             mDialogAnimation.cancel();
             mDialogAnimation = null;
@@ -145,7 +152,7 @@
         Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId,
                 animAttr, /* translucent= */ false);
         if (animation == null) {
-            Log.e(TAG, "Failed to load animation " + animAttr);
+            Log.e(mTag, "Failed to load animation " + animAttr);
         }
         return animation;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java
new file mode 100644
index 0000000..7eea446
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.compatui;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+/**
+ * A component which can provide a {@link View} to use as a container for a Dialog
+ */
+public interface DialogContainerSupplier {
+
+    /**
+     * @return The {@link View} to use as a container for a Dialog
+     */
+    View getDialogContainerView();
+
+    /**
+     * @return The {@link Drawable} to use as background of the dialog.
+     */
+    Drawable getBackgroundDimDrawable();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index 2e0b09e..9232f36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.compatui.DialogContainerSupplier;
 
 /**
  * Container for Letterbox Education Dialog and background dim.
@@ -33,11 +34,7 @@
  * <p>This layout should fill the entire task and the background around the dialog acts as the
  * background dim which dismisses the dialog when clicked.
  */
-class LetterboxEduDialogLayout extends ConstraintLayout {
-
-    // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
-    // 204 is simply 255 * 0.8.
-    static final int BACKGROUND_DIM_ALPHA = 204;
+class LetterboxEduDialogLayout extends ConstraintLayout implements DialogContainerSupplier {
 
     private View mDialogContainer;
     private TextView mDialogTitle;
@@ -60,18 +57,20 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    View getDialogContainer() {
+    @Override
+    public View getDialogContainerView() {
         return mDialogContainer;
     }
 
+    @Override
+    public Drawable getBackgroundDimDrawable() {
+        return mBackgroundDim;
+    }
+
     TextView getDialogTitle() {
         return mDialogTitle;
     }
 
-    Drawable getBackgroundDim() {
-        return mBackgroundDim;
-    }
-
     /**
      * Register a callback for the dismiss button and background dim.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index 867d0ef..c14c009 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -37,6 +37,7 @@
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
+import com.android.wm.shell.compatui.DialogAnimationController;
 import com.android.wm.shell.transition.Transitions;
 
 /**
@@ -63,7 +64,7 @@
      */
     private final SharedPreferences mSharedPreferences;
 
-    private final LetterboxEduAnimationController mAnimationController;
+    private final DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
 
     private final Transitions mTransitions;
 
@@ -96,14 +97,17 @@
             DisplayLayout displayLayout, Transitions transitions,
             Runnable onDismissCallback, DockStateReader dockStateReader) {
         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
-                onDismissCallback, new LetterboxEduAnimationController(context), dockStateReader);
+                onDismissCallback,
+                new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
+                dockStateReader);
     }
 
     @VisibleForTesting
     LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
-            LetterboxEduAnimationController animationController, DockStateReader dockStateReader) {
+            DialogAnimationController<LetterboxEduDialogLayout> animationController,
+            DockStateReader dockStateReader) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
@@ -160,7 +164,7 @@
         if (mLayout == null) {
             return;
         }
-        final View dialogContainer = mLayout.getDialogContainer();
+        final View dialogContainer = mLayout.getDialogContainerView();
         MarginLayoutParams marginParams = (MarginLayoutParams) dialogContainer.getLayoutParams();
 
         final Rect taskBounds = getTaskBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 6b59e31..d7cb490 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.unfold;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -56,6 +54,12 @@
     private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
     private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
 
+    /**
+     * Indicates whether we're in stage change process. This should be set to {@code true} in
+     * {@link #onStateChangeStarted()} and {@code false} in {@link #onStateChangeFinished()}.
+     */
+    private boolean mIsInStageChange;
+
     public UnfoldAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull TransactionPool transactionPool,
@@ -123,7 +127,7 @@
                 animator.onTaskChanged(taskInfo);
             } else {
                 // Became inapplicable
-                resetTask(animator, taskInfo);
+                maybeResetTask(animator, taskInfo);
                 animator.onTaskVanished(taskInfo);
                 mAnimatorsByTaskId.remove(taskInfo.taskId);
             }
@@ -154,7 +158,7 @@
         final boolean isCurrentlyApplicable = animator != null;
 
         if (isCurrentlyApplicable) {
-            resetTask(animator, taskInfo);
+            maybeResetTask(animator, taskInfo);
             animator.onTaskVanished(taskInfo);
             mAnimatorsByTaskId.remove(taskInfo.taskId);
         }
@@ -166,6 +170,7 @@
             return;
         }
 
+        mIsInStageChange = true;
         SurfaceControl.Transaction transaction = null;
         for (int i = 0; i < mAnimators.size(); i++) {
             final UnfoldTaskAnimator animator = mAnimators.get(i);
@@ -219,11 +224,12 @@
         transaction.apply();
 
         mTransactionPool.release(transaction);
+        mIsInStageChange = false;
     }
 
-    private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
-        if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
-            // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
+    private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
+        if (!mIsInStageChange) {
+            // No need to resetTask if there is no ongoing state change.
             return;
         }
         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
index 1dee88c..a58620d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
@@ -68,11 +68,11 @@
 
     @Test
     public void testOnFinishInflate() {
-        assertEquals(mLayout.getDialogContainer(),
+        assertEquals(mLayout.getDialogContainerView(),
                 mLayout.findViewById(R.id.letterbox_education_dialog_container));
         assertEquals(mLayout.getDialogTitle(),
                 mLayout.findViewById(R.id.letterbox_education_dialog_title));
-        assertEquals(mLayout.getBackgroundDim(), mLayout.getBackground());
+        assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground());
         assertEquals(mLayout.getBackground().getAlpha(), 0);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index 16517c0..14190f1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -56,6 +56,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.DialogAnimationController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
@@ -98,7 +99,7 @@
     @Captor
     private ArgumentCaptor<Runnable> mRunOnIdleCaptor;
 
-    @Mock private LetterboxEduAnimationController mAnimationController;
+    @Mock private DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
     @Mock private SurfaceControlViewHost mViewHost;
@@ -366,7 +367,7 @@
         assertThat(params.width).isEqualTo(expectedWidth);
         assertThat(params.height).isEqualTo(expectedHeight);
         MarginLayoutParams dialogParams =
-                (MarginLayoutParams) layout.getDialogContainer().getLayoutParams();
+                (MarginLayoutParams) layout.getDialogContainerView().getLayoutParams();
         int verticalMargin = (int) mContext.getResources().getDimension(
                 R.dimen.letterbox_education_dialog_margin);
         assertThat(dialogParams.topMargin).isEqualTo(verticalMargin + expectedExtraTopMargin);
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 911315f..75f61f5 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -21,6 +21,7 @@
         <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
     </target_preparer>
     <option name="test-suite-tag" value="apct" />
+    <option name="not-shardable" value="true" />
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
         <option name="module-name" value="hwui_unit_tests" />
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 6a3bc8f..c835849 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -576,14 +576,22 @@
     LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix));
 
     skcms_TransferFunction transferParams;
-    // We can only handle numerical transfer functions at the moment
-    LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams));
+    decodeColorSpace->transferFn(&transferParams);
+    auto res = skcms_TransferFunction_getType(&transferParams);
+    LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
 
-    jobject params = env->NewObject(gTransferParameters_class,
-            gTransferParameters_constructorMethodID,
-            transferParams.a, transferParams.b, transferParams.c,
-            transferParams.d, transferParams.e, transferParams.f,
-            transferParams.g);
+    jobject params;
+    if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
+        params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+                                transferParams.a, transferParams.b, transferParams.c,
+                                transferParams.d, transferParams.e, transferParams.f,
+                                transferParams.g, true);
+    } else {
+        params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+                                transferParams.a, transferParams.b, transferParams.c,
+                                transferParams.d, transferParams.e, transferParams.f,
+                                transferParams.g, false);
+    }
 
     jfloatArray xyzArray = env->NewFloatArray(9);
     jfloat xyz[9] = {
@@ -808,8 +816,8 @@
 
     gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
             "android/graphics/ColorSpace$Rgb$TransferParameters"));
-    gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class,
-            "<init>", "(DDDDDDD)V");
+    gTransferParameters_constructorMethodID =
+            GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V");
 
     gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
     gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 7b9a93f..3aac48d 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -44,10 +44,16 @@
     sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
             genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
-                             vertexOffset, nullptr, skRect)
-                        .mesh;
-    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+    auto meshResult = SkMesh::Make(
+            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+    if (!meshResult.error.isEmpty()) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+    }
+
+    auto meshPtr = std::make_unique<MeshWrapper>(
+            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
 
@@ -61,11 +67,17 @@
     sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
             genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
-                                    vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
-                                    skRect)
-                        .mesh;
-    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+
+    auto meshResult = SkMesh::MakeIndexed(
+            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+            skIndexBuffer, indexCount, indexOffset,
+            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+    if (!meshResult.error.isEmpty()) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+    }
+    auto meshPtr = std::make_unique<MeshWrapper>(
+            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
 
@@ -139,22 +151,22 @@
     }
 }
 
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
                                 jfloat value1, jfloat value2, jfloat value3, jfloat value4,
                                 jint count) {
-    auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, uniformName);
     const float values[4] = {value1, value2, value3, value4};
-    nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
 }
 
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
                                      jfloatArray jvalues, jboolean isColor) {
-    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, jUniformName);
     AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
-    nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
-                              isColor);
+    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+                              autoValues.length(), isColor);
 }
 
 static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -171,20 +183,21 @@
     }
 }
 
-static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
                               jint value1, jint value2, jint value3, jint value4, jint count) {
-    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, uniformName);
     const int values[4] = {value1, value2, value3, value4};
-    nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
 }
 
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
                                    jintArray values) {
-    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, uniformName);
     AutoJavaIntArray autoValues(env, values, 0);
-    nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+                            autoValues.length());
 }
 
 static void MeshWrapper_destroy(MeshWrapper* wrapper) {
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
index aa014a5..7a73f2d 100644
--- a/libs/hwui/jni/Mesh.h
+++ b/libs/hwui/jni/Mesh.h
@@ -239,6 +239,7 @@
 
     explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
         fMeshSpec = sk_sp(meshSpec);
+        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
     }
 
     sk_sp<SkData> fUniforms;
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index 619a3ed..ae9792d 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -78,7 +78,6 @@
     auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
                                                     SkString(skVertexShader.c_str()),
                                                     SkString(skFragmentShader.c_str()));
-
     if (meshSpecResult.specification.get() == nullptr) {
         jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
     }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 64839d0..78ae5cf 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -475,6 +475,7 @@
 void CanvasContext::notifyFramePending() {
     ATRACE_CALL();
     mRenderThread.pushBackFrameCallback(this);
+    sendLoadResetHint();
 }
 
 void CanvasContext::draw() {
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index a8b0dc2..8b96974 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -37,11 +37,14 @@
  * Events are delivered to registered instances of {@link Callback}.
  */
 public final class GnssMeasurementsEvent implements Parcelable {
+    private final int mFlag;
     private final GnssClock mClock;
     private final List<GnssMeasurement> mMeasurements;
     private final List<GnssAutomaticGainControl> mGnssAgcs;
     private final boolean mIsFullTracking;
 
+    private static final int HAS_FULL_TRACKING = 1;
+
     /**
      * Used for receiving GNSS satellite measurements from the GNSS engine.
      * Each measurement contains raw and computed data identifying a satellite.
@@ -123,10 +126,12 @@
     /**
      * Create a {@link GnssMeasurementsEvent} instance with a full list of parameters.
      */
-    private GnssMeasurementsEvent(@NonNull GnssClock clock,
+    private GnssMeasurementsEvent(int flag,
+            @NonNull GnssClock clock,
             @NonNull List<GnssMeasurement> measurements,
             @NonNull List<GnssAutomaticGainControl> agcs,
             boolean isFullTracking) {
+        mFlag = flag;
         mMeasurements = measurements;
         mGnssAgcs = agcs;
         mClock = clock;
@@ -168,22 +173,32 @@
      *
      * False indicates that the GNSS chipset may optimize power via duty cycling, constellations and
      * frequency limits, etc.
+     *
+     * <p>The value is only available if {@link #hasFullTracking()} is {@code true}.
      */
-    public boolean getIsFullTracking() {
+    public boolean isFullTracking() {
         return mIsFullTracking;
     }
 
+    /**
+     * Return {@code true} if {@link #isFullTracking()} is available, {@code false} otherwise.
+     */
+    public boolean hasFullTracking() {
+        return (mFlag & HAS_FULL_TRACKING) == HAS_FULL_TRACKING;
+    }
+
     public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
             new Creator<GnssMeasurementsEvent>() {
         @Override
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
+            int flag = in.readInt();
             GnssClock clock = in.readParcelable(getClass().getClassLoader(),
                     android.location.GnssClock.class);
             List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
             List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
                     GnssAutomaticGainControl.CREATOR);
             boolean isFullTracking = in.readBoolean();
-            return new GnssMeasurementsEvent(clock, measurements, agcs, isFullTracking);
+            return new GnssMeasurementsEvent(flag, clock, measurements, agcs, isFullTracking);
         }
 
         @Override
@@ -199,6 +214,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mFlag);
         parcel.writeParcelable(mClock, flags);
         parcel.writeTypedList(mMeasurements);
         parcel.writeTypedList(mGnssAgcs);
@@ -211,13 +227,16 @@
         builder.append(mClock);
         builder.append(' ').append(mMeasurements.toString());
         builder.append(' ').append(mGnssAgcs.toString());
-        builder.append(" isFullTracking=").append(mIsFullTracking);
+        if (hasFullTracking()) {
+            builder.append(" isFullTracking=").append(mIsFullTracking);
+        }
         builder.append("]");
         return builder.toString();
     }
 
     /** Builder for {@link GnssMeasurementsEvent} */
     public static final class Builder {
+        private int mFlag;
         private GnssClock mClock;
         private List<GnssMeasurement> mMeasurements;
         private List<GnssAutomaticGainControl> mGnssAgcs;
@@ -237,10 +256,11 @@
          * {@link GnssMeasurementsEvent}.
          */
         public Builder(@NonNull GnssMeasurementsEvent event) {
+            mFlag = event.mFlag;
             mClock = event.getClock();
             mMeasurements = (List<GnssMeasurement>) event.getMeasurements();
             mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls();
-            mIsFullTracking = event.getIsFullTracking();
+            mIsFullTracking = event.isFullTracking();
         }
 
         /**
@@ -313,15 +333,26 @@
          * and frequency limits, etc.
          */
         @NonNull
-        public Builder setIsFullTracking(boolean isFullTracking) {
+        public Builder setFullTracking(boolean isFullTracking) {
+            mFlag |= HAS_FULL_TRACKING;
             mIsFullTracking = isFullTracking;
             return this;
         }
 
+        /**
+         * Clears the full tracking mode indicator.
+         */
+        @NonNull
+        public Builder clearFullTracking() {
+            mFlag &= ~HAS_FULL_TRACKING;
+            return this;
+        }
+
         /** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */
         @NonNull
         public GnssMeasurementsEvent build() {
-            return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs, mIsFullTracking);
+            return new GnssMeasurementsEvent(mFlag, mClock, mMeasurements, mGnssAgcs,
+                    mIsFullTracking);
         }
     }
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index fdd6233..19610a93 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1841,8 +1841,7 @@
      * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
      * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
      * @param strategy the strategy to query
-     * @return the preferred device for that strategy, or null if none was ever set or if the
-     *    strategy is invalid
+     * @return list of the preferred devices for that strategy
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -1859,6 +1858,76 @@
 
     /**
      * @hide
+     * Set a device as non-default for a given strategy, i.e. the audio routing to be avoided by
+     * this audio strategy.
+     * <p>Use
+     * {@link #removeDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * to cancel setting this preference for this strategy.</p>
+     * @param strategy the audio strategy whose routing will be affected
+     * @param device the audio device to not route to when available
+     * @return true if the operation was successful, false otherwise
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean setDeviceAsNonDefaultForStrategy(@NonNull AudioProductStrategy strategy,
+                                                    @NonNull AudioDeviceAttributes device) {
+        Objects.requireNonNull(strategy);
+        Objects.requireNonNull(device);
+        try {
+            final int status =
+                    getService().setDeviceAsNonDefaultForStrategy(strategy.getId(), device);
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Removes the audio device(s) from the non-default device list previously set with
+     * {@link #setDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * @param strategy the audio strategy whose routing will be affected
+     * @param device the audio device to remove from the non-default device list
+     * @return true if the operation was successful, false otherwise (invalid strategy, or no
+     *     device set for example)
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean removeDeviceAsNonDefaultForStrategy(@NonNull AudioProductStrategy strategy,
+                                                       @NonNull AudioDeviceAttributes device) {
+        Objects.requireNonNull(strategy);
+        Objects.requireNonNull(device);
+        try {
+            final int status =
+                    getService().removeDeviceAsNonDefaultForStrategy(strategy.getId(), device);
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Gets the audio device(s) from the non-default device list previously set with
+     * {@link #setDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * @param strategy the audio strategy to query
+     * @return list of non-default devices for the strategy
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @NonNull
+    public List<AudioDeviceAttributes> getNonDefaultDevicesForStrategy(
+            @NonNull AudioProductStrategy strategy) {
+        Objects.requireNonNull(strategy);
+        try {
+            return getService().getNonDefaultDevicesForStrategy(strategy.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Interface to be notified of changes in the preferred audio device set for a given audio
      * strategy.
      * <p>Note that this listener will only be invoked whenever
@@ -1892,9 +1961,11 @@
      * Interface to be notified of changes in the preferred audio devices set for a given audio
      * strategy.
      * <p>Note that this listener will only be invoked whenever
-     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
-     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
-     * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)},
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)},
+     * {@link #setDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)},
+     * {@link #removeDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * or {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
      * preferred device(s). It will not be invoked directly after registration with
      * {@link #addOnPreferredDevicesForStrategyChangedListener(
      * Executor, OnPreferredDevicesForStrategyChangedListener)}
@@ -1902,7 +1973,6 @@
      * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      * @see #setPreferredDevicesForStrategy(AudioProductStrategy, List)
      * @see #removePreferredDeviceForStrategy(AudioProductStrategy)
-     * @see #getPreferredDeviceForStrategy(AudioProductStrategy)
      * @see #getPreferredDevicesForStrategy(AudioProductStrategy)
      */
     @SystemApi
@@ -1966,30 +2036,9 @@
             throws SecurityException {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(listener);
-        synchronized (mPrefDevListenerLock) {
-            if (hasPrefDevListener(listener)) {
-                throw new IllegalArgumentException(
-                        "attempt to call addOnPreferredDevicesForStrategyChangedListener() "
-                                + "on a previously registered listener");
-            }
-            // lazy initialization of the list of strategy-preferred device listener
-            if (mPrefDevListeners == null) {
-                mPrefDevListeners = new ArrayList<>();
-            }
-            final int oldCbCount = mPrefDevListeners.size();
-            mPrefDevListeners.add(new PrefDevListenerInfo(listener, executor));
-            if (oldCbCount == 0 && mPrefDevListeners.size() > 0) {
-                // register binder for callbacks
-                if (mPrefDevDispatcherStub == null) {
-                    mPrefDevDispatcherStub = new StrategyPreferredDevicesDispatcherStub();
-                }
-                try {
-                    getService().registerStrategyPreferredDevicesDispatcher(mPrefDevDispatcherStub);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-        }
+        mPrefDevListenerMgr.addListener(
+                executor, listener, "addOnPreferredDevicesForStrategyChangedListener",
+                () -> new StrategyPreferredDevicesDispatcherStub());
     }
 
     /**
@@ -2002,106 +2051,145 @@
     public void removeOnPreferredDevicesForStrategyChangedListener(
             @NonNull OnPreferredDevicesForStrategyChangedListener listener) {
         Objects.requireNonNull(listener);
-        synchronized (mPrefDevListenerLock) {
-            if (!removePrefDevListener(listener)) {
-                throw new IllegalArgumentException(
-                        "attempt to call removeOnPreferredDeviceForStrategyChangedListener() "
-                                + "on an unregistered listener");
-            }
-            if (mPrefDevListeners.size() == 0) {
-                // unregister binder for callbacks
-                try {
-                    getService().unregisterStrategyPreferredDevicesDispatcher(
-                            mPrefDevDispatcherStub);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                } finally {
-                    mPrefDevDispatcherStub = null;
-                    mPrefDevListeners = null;
-                }
-            }
-        }
+        mPrefDevListenerMgr.removeListener(
+                listener, "removeOnPreferredDevicesForStrategyChangedListener");
     }
 
-
-    private final Object mPrefDevListenerLock = new Object();
     /**
-     * List of listeners for preferred device for strategy and their associated Executor.
-     * List is lazy-initialized on first registration
+     * @hide
+     * Interface to be notified of changes in the non-default audio devices set for a given audio
+     * strategy.
+     * <p>Note that this listener will only be invoked whenever
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)},
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)},
+     * {@link #setDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)},
+     * {@link #removeDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * or {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
+     * non-default device(s). It will not be invoked directly after registration with
+     * {@link #addOnNonDefaultDevicesForStrategyChangedListener(
+     * Executor, OnNonDefaultDevicesForStrategyChangedListener)}
+     * to indicate which strategies had preferred devices at the time of registration.</p>
+     * @see #setDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see #removeDeviceAsNonDefaultForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      */
-    @GuardedBy("mPrefDevListenerLock")
-    private @Nullable ArrayList<PrefDevListenerInfo> mPrefDevListeners;
-
-    private static class PrefDevListenerInfo {
-        final @NonNull OnPreferredDevicesForStrategyChangedListener mListener;
-        final @NonNull Executor mExecutor;
-        PrefDevListenerInfo(OnPreferredDevicesForStrategyChangedListener listener, Executor exe) {
-            mListener = listener;
-            mExecutor = exe;
-        }
+    @SystemApi
+    public interface OnNonDefaultDevicesForStrategyChangedListener {
+        /**
+         * Called on the listener to indicate that the non-default audio devices for the given
+         * strategy has changed.
+         * @param strategy the {@link AudioProductStrategy} whose non-default device changed
+         * @param devices a list of newly set non-default audio devices
+         */
+        void onNonDefaultDevicesForStrategyChanged(@NonNull AudioProductStrategy strategy,
+                                                   @NonNull List<AudioDeviceAttributes> devices);
     }
 
-    @GuardedBy("mPrefDevListenerLock")
-    private StrategyPreferredDevicesDispatcherStub mPrefDevDispatcherStub;
+    /**
+     * @hide
+     * Adds a listener for being notified of changes to the non-default audio devices for
+     * strategies.
+     * @param executor
+     * @param listener
+     * @throws SecurityException if the caller doesn't hold the required permission
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void addOnNonDefaultDevicesForStrategyChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnNonDefaultDevicesForStrategyChangedListener listener)
+            throws SecurityException {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+
+        mNonDefDevListenerMgr.addListener(
+                executor, listener, "addOnNonDefaultDevicesForStrategyChangedListener",
+                () -> new StrategyNonDefaultDevicesDispatcherStub());
+    }
+
+    /**
+     * @hide
+     * Removes a previously added listener of changes to the non-default audio device for
+     * strategies.
+     * @param listener
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void removeOnNonDefaultDevicesForStrategyChangedListener(
+            @NonNull OnNonDefaultDevicesForStrategyChangedListener listener) {
+        Objects.requireNonNull(listener);
+        mNonDefDevListenerMgr.removeListener(
+                listener, "removeOnNonDefaultDevicesForStrategyChangedListener");
+    }
+
+    /**
+     * Manages the OnPreferredDevicesForStrategyChangedListener listeners and the
+     * StrategyPreferredDevicesDispatcherStub
+     */
+    private final CallbackUtil.LazyListenerManager<OnPreferredDevicesForStrategyChangedListener>
+            mPrefDevListenerMgr = new CallbackUtil.LazyListenerManager();
+
+    /**
+     * Manages the OnNonDefaultDevicesForStrategyChangedListener listeners and the
+     * StrategyNonDefaultDevicesDispatcherStub
+     */
+    private final CallbackUtil.LazyListenerManager<OnNonDefaultDevicesForStrategyChangedListener>
+            mNonDefDevListenerMgr = new CallbackUtil.LazyListenerManager();
 
     private final class StrategyPreferredDevicesDispatcherStub
-            extends IStrategyPreferredDevicesDispatcher.Stub {
+            extends IStrategyPreferredDevicesDispatcher.Stub
+            implements CallbackUtil.DispatcherStub {
 
         @Override
         public void dispatchPrefDevicesChanged(int strategyId,
                                                @NonNull List<AudioDeviceAttributes> devices) {
-            // make a shallow copy of listeners so callback is not executed under lock
-            final ArrayList<PrefDevListenerInfo> prefDevListeners;
-            synchronized (mPrefDevListenerLock) {
-                if (mPrefDevListeners == null || mPrefDevListeners.size() == 0) {
-                    return;
-                }
-                prefDevListeners = (ArrayList<PrefDevListenerInfo>) mPrefDevListeners.clone();
-            }
             final AudioProductStrategy strategy =
                     AudioProductStrategy.getAudioProductStrategyWithId(strategyId);
-            final long ident = Binder.clearCallingIdentity();
+
+            mPrefDevListenerMgr.callListeners(
+                    (listener) -> listener.onPreferredDevicesForStrategyChanged(strategy, devices));
+        }
+
+        @Override
+        public void register(boolean register) {
             try {
-                for (PrefDevListenerInfo info : prefDevListeners) {
-                    info.mExecutor.execute(() ->
-                            info.mListener.onPreferredDevicesForStrategyChanged(strategy, devices));
+                if (register) {
+                    getService().registerStrategyPreferredDevicesDispatcher(this);
+                } else {
+                    getService().unregisterStrategyPreferredDevicesDispatcher(this);
                 }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
             }
         }
     }
 
-    @GuardedBy("mPrefDevListenerLock")
-    private @Nullable PrefDevListenerInfo getPrefDevListenerInfo(
-            OnPreferredDevicesForStrategyChangedListener listener) {
-        if (mPrefDevListeners == null) {
-            return null;
+    private final class StrategyNonDefaultDevicesDispatcherStub
+            extends IStrategyNonDefaultDevicesDispatcher.Stub
+            implements CallbackUtil.DispatcherStub {
+
+        @Override
+        public void dispatchNonDefDevicesChanged(int strategyId,
+                                                 @NonNull List<AudioDeviceAttributes> devices) {
+            final AudioProductStrategy strategy =
+                    AudioProductStrategy.getAudioProductStrategyWithId(strategyId);
+
+            mNonDefDevListenerMgr.callListeners(
+                    (listener) -> listener.onNonDefaultDevicesForStrategyChanged(
+                            strategy, devices));
         }
-        for (PrefDevListenerInfo info : mPrefDevListeners) {
-            if (info.mListener == listener) {
-                return info;
+
+        @Override
+        public void register(boolean register) {
+            try {
+                if (register) {
+                    getService().registerStrategyNonDefaultDevicesDispatcher(this);
+                } else {
+                    getService().unregisterStrategyNonDefaultDevicesDispatcher(this);
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
             }
         }
-        return null;
-    }
-
-    @GuardedBy("mPrefDevListenerLock")
-    private boolean hasPrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) {
-        return getPrefDevListenerInfo(listener) != null;
-    }
-
-    @GuardedBy("mPrefDevListenerLock")
-    /**
-     * @return true if the listener was removed from the list
-     */
-    private boolean removePrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) {
-        final PrefDevListenerInfo infoToRemove = getPrefDevListenerInfo(listener);
-        if (infoToRemove != null) {
-            mPrefDevListeners.remove(infoToRemove);
-            return true;
-        }
-        return false;
     }
 
     //====================================================================
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index a743586..9339c3d 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2063,12 +2063,46 @@
 
     /**
      * @hide
+     * Remove device as role for product strategy.
+     * @param strategy the id of the strategy to configure
+     * @param role the role of the devices
+     * @param devices the list of devices to be removed as role for the given strategy
+     * @return {@link #SUCCESS} if successfully set
+     */
+    public static int removeDevicesRoleForStrategy(
+            int strategy, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        if (devices.isEmpty()) {
+            return BAD_VALUE;
+        }
+        int[] types = new int[devices.size()];
+        String[] addresses = new String[devices.size()];
+        for (int i = 0; i < devices.size(); ++i) {
+            types[i] = devices.get(i).getInternalType();
+            addresses[i] = devices.get(i).getAddress();
+        }
+        return removeDevicesRoleForStrategy(strategy, role, types, addresses);
+    }
+
+    /**
+     * @hide
      * Remove devices as role for the strategy
      * @param strategy the id of the strategy to configure
      * @param role the role of the devices
+     * @param types all device types
+     * @param addresses all device addresses
+     * @return {@link #SUCCESS} if successfully removed
+     */
+    public static native int removeDevicesRoleForStrategy(
+            int strategy, int role, @NonNull int[] types, @NonNull String[] addresses);
+
+    /**
+     * @hide
+     * Remove all devices as role for the strategy
+     * @param strategy the id of the strategy to configure
+     * @param role the role of the devices
      * @return {@link #SUCCESS} if successfully removed
      */
-    public static native int removeDevicesRoleForStrategy(int strategy, int role);
+    public static native int clearDevicesRoleForStrategy(int strategy, int role);
 
     /**
      * @hide
diff --git a/media/java/android/media/CallbackUtil.java b/media/java/android/media/CallbackUtil.java
index 2b5fd25..f0280da 100644
--- a/media/java/android/media/CallbackUtil.java
+++ b/media/java/android/media/CallbackUtil.java
@@ -183,7 +183,7 @@
 
         if (!removeListener(listener, listeners)) {
             throw new IllegalArgumentException("attempt to call " + methodName
-                    + "on an unregistered listener");
+                    + " on an unregistered listener");
         }
         if (listeners.size() == 0) {
             unregisterStub.accept(dispatchStub);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0f63cc4..4b36237 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -42,6 +42,7 @@
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
 import android.media.IStrategyPreferredDevicesDispatcher;
+import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.ISpatializerCallback;
 import android.media.ISpatializerHeadTrackerAvailableCallback;
 import android.media.ISpatializerHeadTrackingModeCallback;
@@ -330,7 +331,8 @@
 
     boolean isCallScreeningModeSupported();
 
-    int setPreferredDevicesForStrategy(in int strategy, in List<AudioDeviceAttributes> device);
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    int setPreferredDevicesForStrategy(in int strategy, in List<AudioDeviceAttributes> devices);
 
     @EnforcePermission("MODIFY_AUDIO_ROUTING")
     int removePreferredDevicesForStrategy(in int strategy);
@@ -338,6 +340,15 @@
     @EnforcePermission("MODIFY_AUDIO_ROUTING")
     List<AudioDeviceAttributes> getPreferredDevicesForStrategy(in int strategy);
 
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    int setDeviceAsNonDefaultForStrategy(in int strategy, in AudioDeviceAttributes device);
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    int removeDeviceAsNonDefaultForStrategy(in int strategy, in AudioDeviceAttributes device);
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    List<AudioDeviceAttributes> getNonDefaultDevicesForStrategy(in int strategy);
+
     List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes);
 
     List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes);
@@ -351,6 +362,12 @@
     oneway void unregisterStrategyPreferredDevicesDispatcher(
             IStrategyPreferredDevicesDispatcher dispatcher);
 
+    void registerStrategyNonDefaultDevicesDispatcher(
+            IStrategyNonDefaultDevicesDispatcher dispatcher);
+
+    oneway void unregisterStrategyNonDefaultDevicesDispatcher(
+            IStrategyNonDefaultDevicesDispatcher dispatcher);
+
     oneway void setRttEnabled(in boolean rttEnabled);
 
     @EnforcePermission("MODIFY_AUDIO_ROUTING")
diff --git a/media/java/android/media/IStrategyNonDefaultDevicesDispatcher.aidl b/media/java/android/media/IStrategyNonDefaultDevicesDispatcher.aidl
new file mode 100644
index 0000000..59239cb
--- /dev/null
+++ b/media/java/android/media/IStrategyNonDefaultDevicesDispatcher.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.media;
+
+import android.media.AudioDeviceAttributes;
+
+/**
+ * AIDL for AudioService to signal non-daefault devices updates for audio strategies.
+ *
+ * {@hide}
+ */
+oneway interface IStrategyNonDefaultDevicesDispatcher {
+
+    void dispatchNonDefDevicesChanged(int strategyId, in List<AudioDeviceAttributes> devices);
+
+}
+
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index d74df7a..6a5b290 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -198,19 +199,22 @@
         @NonNull private final String mRouteId;
         @Flags private final int mFlags;
         @DisableReason private final int mDisableReason;
+        private final int mSessionParticipantCount;
 
         private Item(@NonNull Builder builder) {
             mRouteId = builder.mRouteId;
             mFlags = builder.mFlags;
             mDisableReason = builder.mDisableReason;
+            mSessionParticipantCount = builder.mSessionParticipantCount;
         }
 
         private Item(Parcel in) {
-            String routeId = in.readString();
-            Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
-            mRouteId = routeId;
+            mRouteId = in.readString();
+            Preconditions.checkArgument(!TextUtils.isEmpty(mRouteId));
             mFlags = in.readInt();
             mDisableReason = in.readInt();
+            mSessionParticipantCount = in.readInt();
+            Preconditions.checkArgument(mSessionParticipantCount >= 0);
         }
 
         /** Returns the id of the route that corresponds to this route listing preference item. */
@@ -244,6 +248,17 @@
             return mDisableReason;
         }
 
+        /**
+         * Returns a non-negative number of participants in the ongoing session (if any) on the
+         * corresponding route.
+         *
+         * <p>The system ignores this value if zero, or if {@link #getFlags()} does not include
+         * {@link #FLAG_ONGOING_SESSION}.
+         */
+        public int getSessionParticipantCount() {
+            return mSessionParticipantCount;
+        }
+
         // Item Parcelable implementation.
 
         @Override
@@ -256,6 +271,7 @@
             dest.writeString(mRouteId);
             dest.writeInt(mFlags);
             dest.writeInt(mDisableReason);
+            dest.writeInt(mSessionParticipantCount);
         }
 
         // Equals and hashCode.
@@ -271,12 +287,13 @@
             Item item = (Item) other;
             return mRouteId.equals(item.mRouteId)
                     && mFlags == item.mFlags
-                    && mDisableReason == item.mDisableReason;
+                    && mDisableReason == item.mDisableReason
+                    && mSessionParticipantCount == item.mSessionParticipantCount;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mRouteId, mFlags, mDisableReason);
+            return Objects.hash(mRouteId, mFlags, mDisableReason, mSessionParticipantCount);
         }
 
         /** Builder for {@link Item}. */
@@ -285,6 +302,7 @@
             private final String mRouteId;
             private int mFlags;
             private int mDisableReason;
+            private int mSessionParticipantCount;
 
             /**
              * Constructor.
@@ -311,6 +329,17 @@
                 return this;
             }
 
+            /** See {@link Item#getSessionParticipantCount()}. */
+            @NonNull
+            public Builder setSessionParticipantCount(
+                    @IntRange(from = 0) int sessionParticipantCount) {
+                Preconditions.checkArgument(
+                        sessionParticipantCount >= 0,
+                        "sessionParticipantCount must be non-negative.");
+                mSessionParticipantCount = sessionParticipantCount;
+                return this;
+            }
+
             /** Creates and returns a new {@link Item} with the given parameters. */
             @NonNull
             public Item build() {
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index e60d537..667a9ae 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -946,6 +946,10 @@
                 id = generateInputId(componentName, mTvInputHardwareInfo);
                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
                 isHardwareInput = true;
+                if (mTvInputHardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
+                    mHdmiDeviceInfo = HdmiDeviceInfo.hardwarePort(
+                            HdmiDeviceInfo.PATH_INVALID, mTvInputHardwareInfo.getHdmiPortId());
+                }
             } else {
                 id = generateInputId(componentName);
                 type = TYPE_TUNER;
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 8568c43..7e9443b 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -154,7 +154,8 @@
 
     /** @hide */
     @IntDef(prefix = "STATUS_",
-            value = {STATUS_DATA_READY, STATUS_LOW_WATER, STATUS_HIGH_WATER, STATUS_OVERFLOW})
+            value = {STATUS_DATA_READY, STATUS_LOW_WATER, STATUS_HIGH_WATER, STATUS_OVERFLOW,
+                    STATUS_NO_DATA})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Status {}
 
@@ -183,6 +184,10 @@
      * discarded.
      */
     public static final int STATUS_OVERFLOW = DemuxFilterStatus.OVERFLOW;
+    /**
+     * The status of a filter that the filter buffer is empty and no filtered data is coming.
+     */
+    public static final int STATUS_NO_DATA = DemuxFilterStatus.NO_DATA;
 
     /** @hide */
     @IntDef(prefix = "SCRAMBLING_STATUS_",
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 7963ff2..63292ce 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -20,4 +20,5 @@
     platform_apis: true,
     certificate: "platform",
     resource_dirs: ["res"],
+    test_suites: ["device-tests"],
 }
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 976a279..88bb30b 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -1,190 +1,328 @@
 <?xml version="1.0" encoding="utf-8"?>
 <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
-    <keyboard-layout android:name="keyboard_layout_english_uk"
-            android:label="@string/keyboard_layout_english_uk_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_uk" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_uk"
+        android:label="@string/keyboard_layout_english_uk_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_uk"
+        android:keyboardLocale="en-Latn-GB"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us"
-            android:label="@string/keyboard_layout_english_us_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us"
+        android:label="@string/keyboard_layout_english_us_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us"
+        android:keyboardLocale="en-Latn,en-Latn-US"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_intl"
-            android:label="@string/keyboard_layout_english_us_intl"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_intl" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_intl"
+        android:label="@string/keyboard_layout_english_us_intl"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_intl"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="extended" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_colemak"
-            android:label="@string/keyboard_layout_english_us_colemak_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_colemak" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_colemak"
+        android:label="@string/keyboard_layout_english_us_colemak_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_colemak"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="colemak" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_dvorak"
-            android:label="@string/keyboard_layout_english_us_dvorak_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_dvorak"
+        android:label="@string/keyboard_layout_english_us_dvorak_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="dvorak" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_workman"
-            android:label="@string/keyboard_layout_english_us_workman_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_workman" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_workman"
+        android:label="@string/keyboard_layout_english_us_workman_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_workman"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="workman" />
 
-    <keyboard-layout android:name="keyboard_layout_german"
-            android:label="@string/keyboard_layout_german_label"
-            android:keyboardLayout="@raw/keyboard_layout_german" />
+    <keyboard-layout
+        android:name="keyboard_layout_german"
+        android:label="@string/keyboard_layout_german_label"
+        android:keyboardLayout="@raw/keyboard_layout_german"
+        android:keyboardLocale="de-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_french"
-            android:label="@string/keyboard_layout_french_label"
-            android:keyboardLayout="@raw/keyboard_layout_french" />
+    <keyboard-layout
+        android:name="keyboard_layout_french"
+        android:label="@string/keyboard_layout_french_label"
+        android:keyboardLayout="@raw/keyboard_layout_french"
+        android:keyboardLocale="fr-Latn-FR"
+        android:keyboardLayoutType="azerty" />
 
-    <keyboard-layout android:name="keyboard_layout_french_ca"
-            android:label="@string/keyboard_layout_french_ca_label"
-            android:keyboardLayout="@raw/keyboard_layout_french_ca" />
+    <keyboard-layout
+        android:name="keyboard_layout_french_ca"
+        android:label="@string/keyboard_layout_french_ca_label"
+        android:keyboardLayout="@raw/keyboard_layout_french_ca"
+        android:keyboardLocale="fr-Latn-CA"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_russian"
-            android:label="@string/keyboard_layout_russian_label"
-            android:keyboardLayout="@raw/keyboard_layout_russian" />
+    <keyboard-layout
+        android:name="keyboard_layout_russian"
+        android:label="@string/keyboard_layout_russian_label"
+        android:keyboardLayout="@raw/keyboard_layout_russian"
+        android:keyboardLocale="ru-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_russian_mac"
-            android:label="@string/keyboard_layout_russian_mac_label"
-            android:keyboardLayout="@raw/keyboard_layout_russian_mac" />
+    <keyboard-layout
+        android:name="keyboard_layout_russian_mac"
+        android:label="@string/keyboard_layout_russian_mac_label"
+        android:keyboardLayout="@raw/keyboard_layout_russian_mac"
+        android:keyboardLocale="ru-Cyrl"
+        android:keyboardLayoutType="extended" />
 
-    <keyboard-layout android:name="keyboard_layout_spanish"
-            android:label="@string/keyboard_layout_spanish_label"
-            android:keyboardLayout="@raw/keyboard_layout_spanish" />
+    <keyboard-layout
+        android:name="keyboard_layout_spanish"
+        android:label="@string/keyboard_layout_spanish_label"
+        android:keyboardLayout="@raw/keyboard_layout_spanish"
+        android:keyboardLocale="es-Latn-ES"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_swiss_french"
-            android:label="@string/keyboard_layout_swiss_french_label"
-            android:keyboardLayout="@raw/keyboard_layout_swiss_french" />
+    <keyboard-layout
+        android:name="keyboard_layout_swiss_french"
+        android:label="@string/keyboard_layout_swiss_french_label"
+        android:keyboardLayout="@raw/keyboard_layout_swiss_french"
+        android:keyboardLocale="fr-Latn-CH"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_swiss_german"
-            android:label="@string/keyboard_layout_swiss_german_label"
-            android:keyboardLayout="@raw/keyboard_layout_swiss_german" />
+    <keyboard-layout
+        android:name="keyboard_layout_swiss_german"
+        android:label="@string/keyboard_layout_swiss_german_label"
+        android:keyboardLayout="@raw/keyboard_layout_swiss_german"
+        android:keyboardLocale="de-Latn-CH"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_belgian"
-            android:label="@string/keyboard_layout_belgian"
-            android:keyboardLayout="@raw/keyboard_layout_belgian" />
+    <keyboard-layout
+        android:name="keyboard_layout_belgian"
+        android:label="@string/keyboard_layout_belgian"
+        android:keyboardLayout="@raw/keyboard_layout_belgian"
+        android:keyboardLocale="fr-Latn-BE"
+        android:keyboardLayoutType="azerty" />
 
-    <keyboard-layout android:name="keyboard_layout_bulgarian"
-            android:label="@string/keyboard_layout_bulgarian"
-            android:keyboardLayout="@raw/keyboard_layout_bulgarian" />
+    <keyboard-layout
+        android:name="keyboard_layout_bulgarian"
+        android:label="@string/keyboard_layout_bulgarian"
+        android:keyboardLayout="@raw/keyboard_layout_bulgarian"
+        android:keyboardLocale="bg-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_bulgarian_phonetic"
-            android:label="@string/keyboard_layout_bulgarian_phonetic"
-            android:keyboardLayout="@raw/keyboard_layout_bulgarian_phonetic" />
+    <keyboard-layout
+        android:name="keyboard_layout_bulgarian_phonetic"
+        android:label="@string/keyboard_layout_bulgarian_phonetic"
+        android:keyboardLayout="@raw/keyboard_layout_bulgarian_phonetic"
+        android:keyboardLocale="bg-Cyrl"
+        android:keyboardLayoutType="extended" />
 
-    <keyboard-layout android:name="keyboard_layout_italian"
-            android:label="@string/keyboard_layout_italian"
-            android:keyboardLayout="@raw/keyboard_layout_italian" />
+    <keyboard-layout
+        android:name="keyboard_layout_italian"
+        android:label="@string/keyboard_layout_italian"
+        android:keyboardLayout="@raw/keyboard_layout_italian"
+        android:keyboardLocale="it-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_danish"
-            android:label="@string/keyboard_layout_danish"
-            android:keyboardLayout="@raw/keyboard_layout_danish" />
+    <keyboard-layout
+        android:name="keyboard_layout_danish"
+        android:label="@string/keyboard_layout_danish"
+        android:keyboardLayout="@raw/keyboard_layout_danish"
+        android:keyboardLocale="da-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_norwegian"
-            android:label="@string/keyboard_layout_norwegian"
-            android:keyboardLayout="@raw/keyboard_layout_norwegian" />
+    <keyboard-layout
+        android:name="keyboard_layout_norwegian"
+        android:label="@string/keyboard_layout_norwegian"
+        android:keyboardLayout="@raw/keyboard_layout_norwegian"
+        android:keyboardLocale="nb-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_swedish"
-            android:label="@string/keyboard_layout_swedish"
-            android:keyboardLayout="@raw/keyboard_layout_swedish" />
+    <keyboard-layout
+        android:name="keyboard_layout_swedish"
+        android:label="@string/keyboard_layout_swedish"
+        android:keyboardLayout="@raw/keyboard_layout_swedish"
+        android:keyboardLocale="sv-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_finnish"
-            android:label="@string/keyboard_layout_finnish"
-            android:keyboardLayout="@raw/keyboard_layout_finnish" />
+    <keyboard-layout
+        android:name="keyboard_layout_finnish"
+        android:label="@string/keyboard_layout_finnish"
+        android:keyboardLayout="@raw/keyboard_layout_finnish"
+        android:keyboardLocale="fi-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_croatian"
-            android:label="@string/keyboard_layout_croatian"
-            android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian" />
+    <keyboard-layout
+        android:name="keyboard_layout_croatian"
+        android:label="@string/keyboard_layout_croatian"
+        android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian"
+        android:keyboardLocale="hr-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_czech"
-            android:label="@string/keyboard_layout_czech"
-            android:keyboardLayout="@raw/keyboard_layout_czech" />
+    <keyboard-layout
+        android:name="keyboard_layout_czech"
+        android:label="@string/keyboard_layout_czech"
+        android:keyboardLayout="@raw/keyboard_layout_czech"
+        android:keyboardLocale="cs-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_czech_qwerty"
-            android:label="@string/keyboard_layout_czech_qwerty"
-            android:keyboardLayout="@raw/keyboard_layout_czech_qwerty" />
+    <keyboard-layout
+        android:name="keyboard_layout_czech_qwerty"
+        android:label="@string/keyboard_layout_czech_qwerty"
+        android:keyboardLayout="@raw/keyboard_layout_czech_qwerty"
+        android:keyboardLocale="cs-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_estonian"
-            android:label="@string/keyboard_layout_estonian"
-            android:keyboardLayout="@raw/keyboard_layout_estonian" />
+    <keyboard-layout
+        android:name="keyboard_layout_estonian"
+        android:label="@string/keyboard_layout_estonian"
+        android:keyboardLayout="@raw/keyboard_layout_estonian"
+        android:keyboardLocale="et-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_hungarian"
-            android:label="@string/keyboard_layout_hungarian"
-            android:keyboardLayout="@raw/keyboard_layout_hungarian" />
+    <keyboard-layout
+        android:name="keyboard_layout_hungarian"
+        android:label="@string/keyboard_layout_hungarian"
+        android:keyboardLayout="@raw/keyboard_layout_hungarian"
+        android:keyboardLocale="hu-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_icelandic"
-            android:label="@string/keyboard_layout_icelandic"
-            android:keyboardLayout="@raw/keyboard_layout_icelandic" />
+    <keyboard-layout
+        android:name="keyboard_layout_icelandic"
+        android:label="@string/keyboard_layout_icelandic"
+        android:keyboardLayout="@raw/keyboard_layout_icelandic"
+        android:keyboardLocale="is-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_brazilian"
-            android:label="@string/keyboard_layout_brazilian"
-            android:keyboardLayout="@raw/keyboard_layout_brazilian" />
+    <keyboard-layout
+        android:name="keyboard_layout_brazilian"
+        android:label="@string/keyboard_layout_brazilian"
+        android:keyboardLayout="@raw/keyboard_layout_brazilian"
+        android:keyboardLocale="pt-Latn-BR"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_portuguese"
-            android:label="@string/keyboard_layout_portuguese"
-            android:keyboardLayout="@raw/keyboard_layout_portuguese" />
+    <keyboard-layout
+        android:name="keyboard_layout_portuguese"
+        android:label="@string/keyboard_layout_portuguese"
+        android:keyboardLayout="@raw/keyboard_layout_portuguese"
+        android:keyboardLocale="pt-Latn-PT"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_slovak"
-            android:label="@string/keyboard_layout_slovak"
-            android:keyboardLayout="@raw/keyboard_layout_slovak" />
+    <keyboard-layout
+        android:name="keyboard_layout_slovak"
+        android:label="@string/keyboard_layout_slovak"
+        android:keyboardLayout="@raw/keyboard_layout_slovak"
+        android:keyboardLocale="sk-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_slovenian"
-            android:label="@string/keyboard_layout_slovenian"
-            android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian" />
+    <keyboard-layout
+        android:name="keyboard_layout_slovenian"
+        android:label="@string/keyboard_layout_slovenian"
+        android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian"
+        android:keyboardLocale="sl-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_turkish"
-            android:label="@string/keyboard_layout_turkish"
-            android:keyboardLayout="@raw/keyboard_layout_turkish" />
+    <keyboard-layout
+        android:name="keyboard_layout_turkish"
+        android:label="@string/keyboard_layout_turkish"
+        android:keyboardLayout="@raw/keyboard_layout_turkish"
+        android:keyboardLocale="tr-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_turkish_f"
-            android:label="@string/keyboard_layout_turkish_f"
-            android:keyboardLayout="@raw/keyboard_layout_turkish_f" />
+    <keyboard-layout
+        android:name="keyboard_layout_turkish"
+        android:label="@string/keyboard_layout_turkish"
+        android:keyboardLayout="@raw/keyboard_layout_turkish"
+        android:keyboardLocale="tr-Latn"
+        android:keyboardLayoutType="turkish_q" />
 
-    <keyboard-layout android:name="keyboard_layout_ukrainian"
-            android:label="@string/keyboard_layout_ukrainian"
-            android:keyboardLayout="@raw/keyboard_layout_ukrainian" />
+    <keyboard-layout
+        android:name="keyboard_layout_turkish_f"
+        android:label="@string/keyboard_layout_turkish_f"
+        android:keyboardLayout="@raw/keyboard_layout_turkish_f"
+        android:keyboardLocale="tr-Latn"
+        android:keyboardLayoutType="turkish_f" />
 
-    <keyboard-layout android:name="keyboard_layout_arabic"
-            android:label="@string/keyboard_layout_arabic"
-            android:keyboardLayout="@raw/keyboard_layout_arabic" />
+    <keyboard-layout
+        android:name="keyboard_layout_ukrainian"
+        android:label="@string/keyboard_layout_ukrainian"
+        android:keyboardLayout="@raw/keyboard_layout_ukrainian"
+        android:keyboardLocale="uk-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_greek"
-            android:label="@string/keyboard_layout_greek"
-            android:keyboardLayout="@raw/keyboard_layout_greek" />
+    <keyboard-layout
+        android:name="keyboard_layout_arabic"
+        android:label="@string/keyboard_layout_arabic"
+        android:keyboardLayout="@raw/keyboard_layout_arabic"
+        android:keyboardLocale="ar-Arab" />
 
-    <keyboard-layout android:name="keyboard_layout_hebrew"
-            android:label="@string/keyboard_layout_hebrew"
-            android:keyboardLayout="@raw/keyboard_layout_hebrew" />
+    <keyboard-layout
+        android:name="keyboard_layout_greek"
+        android:label="@string/keyboard_layout_greek"
+        android:keyboardLayout="@raw/keyboard_layout_greek"
+        android:keyboardLocale="el-Grek" />
 
-    <keyboard-layout android:name="keyboard_layout_lithuanian"
-            android:label="@string/keyboard_layout_lithuanian"
-            android:keyboardLayout="@raw/keyboard_layout_lithuanian" />
+    <keyboard-layout
+        android:name="keyboard_layout_hebrew"
+        android:label="@string/keyboard_layout_hebrew"
+        android:keyboardLayout="@raw/keyboard_layout_hebrew"
+        android:keyboardLocale="iw-Hebr" />
 
-    <keyboard-layout android:name="keyboard_layout_spanish_latin"
-            android:label="@string/keyboard_layout_spanish_latin"
-            android:keyboardLayout="@raw/keyboard_layout_spanish_latin" />
+    <keyboard-layout
+        android:name="keyboard_layout_lithuanian"
+        android:label="@string/keyboard_layout_lithuanian"
+        android:keyboardLayout="@raw/keyboard_layout_lithuanian"
+        android:keyboardLocale="lt-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_latvian"
-            android:label="@string/keyboard_layout_latvian"
-            android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty" />
+    <keyboard-layout
+        android:name="keyboard_layout_spanish_latin"
+        android:label="@string/keyboard_layout_spanish_latin"
+        android:keyboardLayout="@raw/keyboard_layout_spanish_latin"
+        android:keyboardLocale="es-Latn-419"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_persian"
-            android:label="@string/keyboard_layout_persian"
-            android:keyboardLayout="@raw/keyboard_layout_persian" />
+    <keyboard-layout
+        android:name="keyboard_layout_latvian"
+        android:label="@string/keyboard_layout_latvian"
+        android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty"
+        android:keyboardLocale="lv-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_azerbaijani"
-            android:label="@string/keyboard_layout_azerbaijani"
-            android:keyboardLayout="@raw/keyboard_layout_azerbaijani" />
+    <keyboard-layout
+        android:name="keyboard_layout_persian"
+        android:label="@string/keyboard_layout_persian"
+        android:keyboardLayout="@raw/keyboard_layout_persian"
+        android:keyboardLocale="fa-Arab" />
 
-    <keyboard-layout android:name="keyboard_layout_polish"
-            android:label="@string/keyboard_layout_polish"
-            android:keyboardLayout="@raw/keyboard_layout_polish" />
+    <keyboard-layout
+        android:name="keyboard_layout_azerbaijani"
+        android:label="@string/keyboard_layout_azerbaijani"
+        android:keyboardLayout="@raw/keyboard_layout_azerbaijani"
+        android:keyboardLocale="az-Latn-AZ"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_belarusian"
-            android:label="@string/keyboard_layout_belarusian"
-            android:keyboardLayout="@raw/keyboard_layout_belarusian" />
+    <keyboard-layout
+        android:name="keyboard_layout_polish"
+        android:label="@string/keyboard_layout_polish"
+        android:keyboardLayout="@raw/keyboard_layout_polish"
+        android:keyboardLocale="pl-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_mongolian"
-            android:label="@string/keyboard_layout_mongolian"
-            android:keyboardLayout="@raw/keyboard_layout_mongolian" />
+    <keyboard-layout
+        android:name="keyboard_layout_belarusian"
+        android:label="@string/keyboard_layout_belarusian"
+        android:keyboardLayout="@raw/keyboard_layout_belarusian"
+        android:keyboardLocale="be-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_georgian"
-            android:label="@string/keyboard_layout_georgian"
-            android:keyboardLayout="@raw/keyboard_layout_georgian" />
+    <keyboard-layout
+        android:name="keyboard_layout_mongolian"
+        android:label="@string/keyboard_layout_mongolian"
+        android:keyboardLayout="@raw/keyboard_layout_mongolian"
+        android:keyboardLocale="mn-Cyrl" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_georgian"
+        android:label="@string/keyboard_layout_georgian"
+        android:keyboardLayout="@raw/keyboard_layout_georgian"
+        android:keyboardLocale="ka-Geor" />
 </keyboard-layouts>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index fe640ad..fd982f5 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -39,12 +39,13 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
-
     static_libs: [
         "xz-java",
         "androidx.leanback_leanback",
+        "androidx.annotation_annotation",
     ],
 }
 
@@ -56,7 +57,8 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
@@ -75,13 +77,15 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
     static_libs: [
         "xz-java",
         "androidx.leanback_leanback",
+        "androidx.annotation_annotation",
     ],
     aaptflags: ["--product tv"],
 }
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png
new file mode 100644
index 0000000..6e5fbb5
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png
new file mode 100644
index 0000000..3434b2d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png
new file mode 100644
index 0000000..673a509
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png
new file mode 100644
index 0000000..c2a739c
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png
new file mode 100644
index 0000000..9d2bfb1
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png
new file mode 100644
index 0000000..4375bf2d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png
new file mode 100644
index 0000000..6b8aa9d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png
new file mode 100644
index 0000000..2884abe
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png
new file mode 100644
index 0000000..76c35ec
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png
new file mode 100644
index 0000000..f317330
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable/ic_dialog_info.png b/packages/PackageInstaller/res/drawable/ic_dialog_info.png
new file mode 100644
index 0000000..efee1ef
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable/ic_dialog_info.png
Binary files differ
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
new file mode 100644
index 0000000..3ced1db
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/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="?android:attr/buttonBarStyle">
+    <com.android.packageinstaller.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="?android:attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/button2"
+            style="?android: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.packageinstaller.ButtonBarLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml
new file mode 100644
index 0000000..0290624
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml
@@ -0,0 +1,84 @@
+<?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.packageinstaller.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:gravity="start|top"
+    android:orientation="vertical">
+
+    <include layout="@layout/alert_dialog_title_material" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <ScrollView
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <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="?android:attr/dialogPreferredPadding"
+                    android:paddingStart="?android:attr/dialogPreferredPadding"
+                    style="@android: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:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/alert_dialog_button_bar_leanback" />
+</com.android.packageinstaller.AlertDialogLayout>
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml
new file mode 100644
index 0000000..f9668dd
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+
+   <LinearLayout
+       android:id="@+id/leftPanel"
+       android:layout_width="0dp"
+       android:layout_weight="1"
+       android:layout_height="wrap_content"
+       android:orientation="vertical">
+
+    <LinearLayout android:id="@+id/topPanel"
+        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"
+            android:paddingBottom="8dip">
+            <ImageView android:id="@+id/icon"
+                android:layout_width="32dip"
+                android:layout_height="32dip"
+                android:layout_marginEnd="8dip"
+                android:scaleType="fitCenter"
+                android:src="@null" />
+            <com.android.packageinstaller.DialogTitle android:id="@+id/alertTitle"
+                style="?android: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>
+
+    <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"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+            <TextView android:id="@+id/message"
+                style="?android:attr/textAppearanceMedium"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="16dip"
+                android:paddingEnd="16dip"
+                android:paddingTop="16dip"
+                android:paddingBottom="16dip" />
+        </ScrollView>
+    </LinearLayout>
+
+    <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="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/buttonPanel"
+        style="?attr/buttonBarStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical"
+        android:gravity="end">
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layoutDirection="locale"
+            android:orientation="vertical">
+            <Button android:id="@+id/button3"
+                style="?attr/buttonBarNeutralButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+            <Button android:id="@+id/button2"
+                style="?attr/buttonBarNegativeButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+            <Button android:id="@+id/button1"
+                style="?attr/buttonBarPositiveButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+        </LinearLayout>
+     </LinearLayout>
+</LinearLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml
new file mode 100644
index 0000000..e4977e7
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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="?android:attr/buttonBarStyle">
+    <com.android.packageinstaller.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"
+        android:gravity="bottom">
+
+        <Button
+            android:id="@+id/button3"
+            style="?android:attr/buttonBarNeutralButtonStyle"
+            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/button2"
+            style="?android:attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/button1"
+            style="?android:attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </com.android.packageinstaller.ButtonBarLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_material.xml
new file mode 100644
index 0000000..10e9149
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_material.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.packageinstaller.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:gravity="start|top"
+    android:orientation="vertical">
+
+    <include layout="@layout/alert_dialog_title_material" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <ScrollView
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <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="?android:attr/dialogPreferredPadding"
+                    android:paddingStart="?android:attr/dialogPreferredPadding"
+                    style="@android: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:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/alert_dialog_button_bar_material" />
+</com.android.packageinstaller.AlertDialogLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_progress.xml b/packages/PackageInstaller/res/layout/alert_dialog_progress.xml
new file mode 100644
index 0000000..fe06b65
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_progress.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" android:layout_height="match_parent">
+        <ProgressBar android:id="@+id/progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dip"
+            android:layout_marginBottom="1dip"
+            android:layout_marginStart="10dip"
+            android:layout_marginEnd="10dip"
+            android:layout_centerHorizontal="true" />
+        <TextView
+            android:id="@+id/progress_percent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingBottom="12dip"
+            android:layout_marginStart="10dip"
+            android:layout_marginEnd="10dip"
+            android:layout_alignParentStart="true"
+            android:layout_below="@id/progress"
+        />
+        <TextView
+            android:id="@+id/progress_number"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingBottom="12dip"
+            android:layout_marginStart="10dip"
+            android:layout_marginEnd="10dip"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@id/progress"
+        />
+</RelativeLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml
new file mode 100644
index 0000000..fb98259
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:paddingStart="?attr/dialogPreferredPadding"
+    android:paddingTop="@dimen/dialog_padding_top_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:paddingBottom="@dimen/dialog_padding_top_material">
+    <ProgressBar
+        android:id="@+id/progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true" />
+    <TextView
+        android:id="@+id/progress_percent"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_below="@id/progress" />
+    <TextView
+        android:id="@+id/progress_number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_below="@id/progress" />
+</RelativeLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml
new file mode 100644
index 0000000..45d9bb6
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/topPanel"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical">
+
+    <!-- If the client uses a customTitle, it will be added here. -->
+
+    <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="?android:attr/dialogPreferredPadding"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:paddingTop="@dimen/dialog_padding_top_material">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_marginEnd="8dip"
+            android:scaleType="fitCenter"
+            android:src="@null" />
+
+        <com.android.packageinstaller.DialogTitle
+            android:id="@+id/alertTitle"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            style="?android:attr/windowTitleStyle" />
+    </LinearLayout>
+
+    <Space
+        android:id="@+id/titleDividerNoCustom"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dialog_title_divider_material" />
+</LinearLayout>
diff --git a/packages/PackageInstaller/res/layout/progress_dialog.xml b/packages/PackageInstaller/res/layout/progress_dialog.xml
new file mode 100644
index 0000000..0d3cd4a
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/progress_dialog.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 2006, 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:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout android:id="@+id/body"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:baselineAligned="false"
+        android:paddingStart="8dip"
+        android:paddingTop="10dip"
+        android:paddingEnd="8dip"
+        android:paddingBottom="10dip">
+
+        <ProgressBar android:id="@android:id/progress"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:max="10000"
+            android:layout_marginEnd="12dip" />
+
+        <TextView android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/PackageInstaller/res/layout/progress_dialog_material.xml b/packages/PackageInstaller/res/layout/progress_dialog_material.xml
new file mode 100644
index 0000000..2417965
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/progress_dialog_material.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:id="@+id/body"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:baselineAligned="false"
+        android:paddingStart="?attr/dialogPreferredPadding"
+        android:paddingTop="@dimen/dialog_padding_top_material"
+        android:paddingEnd="?attr/dialogPreferredPadding"
+        android:paddingBottom="@dimen/dialog_padding_top_material">
+
+        <ProgressBar
+            android:id="@id/progress"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:max="10000"
+            android:layout_marginEnd="?attr/dialogPreferredPadding" />
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/PackageInstaller/res/layout/select_dialog_item_material.xml b/packages/PackageInstaller/res/layout/select_dialog_item_material.xml
new file mode 100644
index 0000000..b45edc6
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_item_material.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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 layout file is used by the AlertDialog when displaying a list of items.
+    This layout file is inflated and used as the TextView to display individual
+    items.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:textColor="?android:attr/textColorAlertDialogListItem"
+    android:gravity="center_vertical"
+    android:paddingStart="?attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+    android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/layout/select_dialog_material.xml b/packages/PackageInstaller/res/layout/select_dialog_material.xml
new file mode 100644
index 0000000..125b9b8
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_material.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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 layout file is used by the AlertDialog when displaying a list of items.
+    This layout file is inflated and used as the ListView to display the items.
+    Assign an ID so its state will be saved/restored.
+-->
+<view class="com.android.packageinstaller.AlertController$RecycleListView"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@id/select_dialog_listview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:cacheColorHint="@null"
+    android:divider="?attr/listDividerAlertDialog"
+    android:scrollbars="vertical"
+    android:overScrollMode="ifContentScrolls"
+    android:textAlignment="viewStart"
+    android:clipToPadding="false"/>
+    <!--android:paddingBottomNoButtons="@dimen/dialog_list_padding_bottom_no_buttons"
+    android:paddingTopNoTitle="@dimen/dialog_list_padding_top_no_title"/>-->
diff --git a/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml b/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml
new file mode 100644
index 0000000..52f709e
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/listPreferredItemHeightSmall"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?attr/textColorAlertDialogListItem"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/select_dialog_padding_start_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
+    android:drawablePadding="@dimen/select_dialog_drawable_padding_start_material"
+    android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml b/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml
new file mode 100644
index 0000000..8345b18
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/listPreferredItemHeightSmall"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?attr/textColorAlertDialogListItem"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/select_dialog_padding_start_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+    android:drawablePadding="@dimen/select_dialog_drawable_padding_start_material"
+    android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml
index 483b0cf..18320f7 100644
--- a/packages/PackageInstaller/res/values-night/themes.xml
+++ b/packages/PackageInstaller/res/values-night/themes.xml
@@ -18,6 +18,8 @@
 <resources>
 
     <style name="Theme.AlertDialogActivity"
-            parent="@android:style/Theme.DeviceDefault.Dialog.Alert" />
+        parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog</item>
+    </style>
 
 </resources>
diff --git a/packages/PackageInstaller/res/values-television/styles.xml b/packages/PackageInstaller/res/values-television/styles.xml
new file mode 100644
index 0000000..936fff0
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2022 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="AlertDialog.Leanback" parent="@style/AlertDialog">
+        <item name="buttonPanelSideLayout">@layout/alert_dialog_leanback_button_panel_side</item>
+        <item name="layout">@layout/alert_dialog_leanback</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
index 5ae4957..1cc6933 100644
--- a/packages/PackageInstaller/res/values-television/themes.xml
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -17,15 +17,20 @@
 
 <resources>
 
-    <style name="Theme.AlertDialogActivity.NoAnimation">
+    <style name="Theme.AlertDialogActivity.NoAnimation"
+           parent="@style/Theme.AlertDialogActivity.NoActionBar">
         <item name="android:windowAnimationStyle">@null</item>
     </style>
 
     <style name="Theme.AlertDialogActivity"
-           parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+        parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
+    </style>
 
     <style name="Theme.AlertDialogActivity.NoActionBar"
-           parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+        parent="@android:style/Theme.Material.Light.NoActionBar">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
 </resources>
diff --git a/packages/PackageInstaller/res/values/attrs.xml b/packages/PackageInstaller/res/values/attrs.xml
index e220f4c..e3070e2 100644
--- a/packages/PackageInstaller/res/values/attrs.xml
+++ b/packages/PackageInstaller/res/values/attrs.xml
@@ -32,4 +32,49 @@
         <attr name="circle_radius_pressed_percent" format="fraction" />
     </declare-styleable>
     <!-- END: Ported from WearableSupport -->
+    <declare-styleable name="Theme">
+        <attr name="alertDialogCenterButtons" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="AlertDialog">
+        <attr name="fullDark" format="reference|color" />
+        <attr name="topDark" format="reference|color" />
+        <attr name="centerDark" format="reference|color" />
+        <attr name="bottomDark" format="reference|color" />
+        <attr name="fullBright" format="reference|color" />
+        <attr name="topBright" format="reference|color" />
+        <attr name="centerBright" format="reference|color" />
+        <attr name="bottomBright" format="reference|color" />
+        <attr name="bottomMedium" format="reference|color" />
+        <attr name="centerMedium" format="reference|color" />
+        <attr name="layout" format="reference" />
+        <attr name="buttonPanelSideLayout" format="reference" />
+        <attr name="listLayout" format="reference" />
+        <attr name="multiChoiceItemLayout" format="reference" />
+        <attr name="singleChoiceItemLayout" format="reference" />
+        <attr name="listItemLayout" format="reference" />
+        <attr name="progressLayout" format="reference" />
+        <attr name="horizontalProgressLayout" format="reference" />
+        <!-- @hide Not ready for public use. -->
+        <attr name="showTitle" format="boolean" />
+        <!-- Whether fullDark, etc. should use default values if null. -->
+        <attr name="needsDefaultBackgrounds" format="boolean" />
+        <!-- Workaround until we replace AlertController with custom layout. -->
+        <attr name="controllerType">
+            <!-- The default controller. -->
+            <enum name="normal" value="0" />
+            <!-- Controller for micro specific layout. -->
+            <enum name="micro" value="1" />
+        </attr>
+        <!-- Offset when scrolling to a selection. -->
+        <attr name="selectionScrollOffset" format="dimension" />
+    </declare-styleable>
+    <declare-styleable name="ButtonBarLayout">
+        <!-- Whether to automatically stack the buttons when there is not
+             enough space to lay them out side-by-side. -->
+        <attr name="allowStacking" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="TextAppearance">
+        <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
+        <attr name="textSize" format="dimension" />
+    </declare-styleable>
 </resources>
diff --git a/packages/PackageInstaller/res/values/dimens.xml b/packages/PackageInstaller/res/values/dimens.xml
index 112723f..bfea05e 100644
--- a/packages/PackageInstaller/res/values/dimens.xml
+++ b/packages/PackageInstaller/res/values/dimens.xml
@@ -41,4 +41,16 @@
 
     <dimen name="wear_permission_review_pref_padding">8dp</dimen>
     <dimen name="wear_permission_review_icon_size">24dp</dimen>
+
+    <!-- Dialog title height -->
+    <dimen name="alert_dialog_title_height">64dip</dimen>
+    <!-- Dialog button bar height -->
+    <dimen name="alert_dialog_button_bar_height">48dip</dimen>
+    <!-- The amount to offset when scrolling to a selection in an AlertDialog -->
+    <dimen name="config_alertDialogSelectionScrollOffset">0dp</dimen>
+    <dimen name="dialog_padding_top_material">18dp</dimen>
+    <dimen name="dialog_title_divider_material">8dp</dimen>
+    <!-- Dialog padding minus control padding, used to fix alignment. -->
+    <dimen name="select_dialog_padding_start_material">20dp</dimen>
+    <dimen name="select_dialog_drawable_padding_start_material">20dp</dimen>
 </resources>
diff --git a/packages/PackageInstaller/res/values/integers.xml b/packages/PackageInstaller/res/values/integers.xml
new file mode 100644
index 0000000..22ad3a3
--- /dev/null
+++ b/packages/PackageInstaller/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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>
+    <!-- The alert controller to use for alert dialogs. -->
+    <integer name="config_alertDialogController">0</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/styles.xml b/packages/PackageInstaller/res/values/styles.xml
new file mode 100644
index 0000000..ca797e1
--- /dev/null
+++ b/packages/PackageInstaller/res/values/styles.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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="AlertDialog">
+        <item name="fullDark">@empty</item>
+        <item name="topDark">@empty</item>
+        <item name="centerDark">@empty</item>
+        <item name="bottomDark">@empty</item>
+        <item name="fullBright">@empty</item>
+        <item name="topBright">@empty</item>
+        <item name="centerBright">@empty</item>
+        <item name="bottomBright">@empty</item>
+        <item name="bottomMedium">@empty</item>
+        <item name="centerMedium">@empty</item>
+        <item name="layout">@layout/alert_dialog_material</item>
+        <item name="listLayout">@layout/select_dialog_material</item>
+        <item name="progressLayout">@layout/progress_dialog_material</item>
+        <item name="horizontalProgressLayout">@layout/alert_dialog_progress_material</item>
+        <item name="listItemLayout">@layout/select_dialog_item_material</item>
+        <item name="multiChoiceItemLayout">@layout/select_dialog_multichoice_material</item>
+        <item name="singleChoiceItemLayout">@layout/select_dialog_singlechoice_material</item>
+        <item name="controllerType">@integer/config_alertDialogController</item>
+        <item name="selectionScrollOffset">@dimen/config_alertDialogSelectionScrollOffset</item>
+        <item name="needsDefaultBackgrounds">false</item>
+    </style>
+    <style name="TextAppearance">
+        <item name="textSize">16sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index eecf9a1..9a06229 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -23,7 +23,9 @@
     </style>
 
     <style name="Theme.AlertDialogActivity"
-            parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+        parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog</item>
+    </style>
 
     <style name="Theme.AlertDialogActivity.NoActionBar">
         <item name="android:windowActionBar">false</item>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java
new file mode 100644
index 0000000..7947400
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 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.packageinstaller;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An activity that follows the visual style of an AlertDialog.
+ * 
+ * @see #mAlert
+ * @see #setupAlert()
+ */
+public abstract class AlertActivity extends Activity implements DialogInterface {
+
+    public AlertActivity() {
+    }
+
+    /**
+     * The model for the alert.
+     * 
+     */
+    protected AlertController mAlert;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAlert = new AlertController(this, this, getWindow());
+    }
+
+    public void cancel() {
+        finish();
+    }
+
+    public void dismiss() {
+        // This is called after the click, since we finish when handling the
+        // click, don't do that again here.
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return dispatchPopulateAccessibilityEvent(this, event);
+    }
+
+    public static boolean dispatchPopulateAccessibilityEvent(Activity act,
+            AccessibilityEvent event) {
+        event.setClassName(Dialog.class.getName());
+        event.setPackageName(act.getPackageName());
+
+        ViewGroup.LayoutParams params = act.getWindow().getAttributes();
+        boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
+                (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
+        event.setFullScreen(isFullScreen);
+
+        return false;
+    }
+
+    /**
+     * Sets up the alert, including applying the parameters to the alert model,
+     * and installing the alert's content.
+     *
+     * @see #mAlert
+     */
+    protected void setupAlert() {
+        mAlert.installContent();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyDown(keyCode, event)) return true;
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyUp(keyCode, event)) return true;
+        return super.onKeyUp(keyCode, event);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java
new file mode 100644
index 0000000..33f38a6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.view.ViewStub;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.R;
+
+import java.lang.ref.WeakReference;
+
+public class AlertController {
+    private final Context mContext;
+    private final DialogInterface mDialogInterface;
+    protected final Window mWindow;
+
+    private CharSequence mTitle;
+    protected CharSequence mMessage;
+    protected ListView mListView;
+    private View mView;
+
+    private int mViewLayoutResId;
+
+    private int mViewSpacingLeft;
+    private int mViewSpacingTop;
+    private int mViewSpacingRight;
+    private int mViewSpacingBottom;
+    private boolean mViewSpacingSpecified = false;
+
+    private Button mButtonPositive;
+    private CharSequence mButtonPositiveText;
+    private Message mButtonPositiveMessage;
+
+    private Button mButtonNegative;
+    private CharSequence mButtonNegativeText;
+    private Message mButtonNegativeMessage;
+
+    private Button mButtonNeutral;
+    private CharSequence mButtonNeutralText;
+    private Message mButtonNeutralMessage;
+
+    protected ScrollView mScrollView;
+
+    private int mIconId = 0;
+    private Drawable mIcon;
+
+    private ImageView mIconView;
+    private TextView mTitleView;
+    protected TextView mMessageView;
+
+    private ListAdapter mAdapter;
+
+    private int mAlertDialogLayout;
+    private int mButtonPanelSideLayout;
+    private int mListLayout;
+    private int mMultiChoiceItemLayout;
+    private int mSingleChoiceItemLayout;
+    private int mListItemLayout;
+
+    private boolean mShowTitle;
+
+    private Handler mHandler;
+
+    private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final Message m;
+            if (v == mButtonPositive && mButtonPositiveMessage != null) {
+                m = Message.obtain(mButtonPositiveMessage);
+            } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
+                m = Message.obtain(mButtonNegativeMessage);
+            } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
+                m = Message.obtain(mButtonNeutralMessage);
+            } else {
+                m = null;
+            }
+
+            if (m != null) {
+                m.sendToTarget();
+            }
+
+            // Post a message so we dismiss after the above handlers are executed
+            mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
+                    .sendToTarget();
+        }
+    };
+
+    private static final class ButtonHandler extends Handler {
+        // Button clicks have Message.what as the BUTTON{1,2,3} constant
+        private static final int MSG_DISMISS_DIALOG = 1;
+
+        private WeakReference<DialogInterface> mDialog;
+
+        public ButtonHandler(DialogInterface dialog) {
+            mDialog = new WeakReference<>(dialog);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+                case DialogInterface.BUTTON_POSITIVE:
+                case DialogInterface.BUTTON_NEGATIVE:
+                case DialogInterface.BUTTON_NEUTRAL:
+                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
+                    break;
+
+                case MSG_DISMISS_DIALOG:
+                    ((DialogInterface) msg.obj).dismiss();
+            }
+        }
+    }
+
+    public AlertController(Context context, DialogInterface di, Window window) {
+        mContext = context;
+        mDialogInterface = di;
+        mWindow = window;
+        mHandler = new ButtonHandler(di);
+
+        final TypedArray a = context.obtainStyledAttributes(null,
+                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+
+        mAlertDialogLayout = a.getResourceId(
+                R.styleable.AlertDialog_layout, R.layout.alert_dialog_material);
+        mButtonPanelSideLayout = a.getResourceId(
+                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
+        mListLayout = a.getResourceId(
+                R.styleable.AlertDialog_listLayout, R.layout.select_dialog_material);
+
+        mMultiChoiceItemLayout = a.getResourceId(
+                R.styleable.AlertDialog_multiChoiceItemLayout,
+                R.layout.select_dialog_multichoice_material);
+        mSingleChoiceItemLayout = a.getResourceId(
+                R.styleable.AlertDialog_singleChoiceItemLayout,
+                R.layout.select_dialog_singlechoice_material);
+        mListItemLayout = a.getResourceId(
+                R.styleable.AlertDialog_listItemLayout,
+                R.layout.select_dialog_item_material);
+        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
+
+        a.recycle();
+
+        /* We use a custom title so never request a window title */
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+    }
+
+    static boolean canTextInput(View v) {
+        if (v.onCheckIsTextEditor()) {
+            return true;
+        }
+
+        if (!(v instanceof ViewGroup)) {
+            return false;
+        }
+
+        ViewGroup vg = (ViewGroup)v;
+        int i = vg.getChildCount();
+        while (i > 0) {
+            i--;
+            v = vg.getChildAt(i);
+            if (canTextInput(v)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void installContent() {
+        mWindow.setContentView(mAlertDialogLayout);
+        setupView();
+    }
+
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setText(title);
+        }
+        mWindow.setTitle(title);
+    }
+
+    /**
+     * Set the view resource to display in the dialog.
+     */
+    public void setView(int layoutResId) {
+        mView = null;
+        mViewLayoutResId = layoutResId;
+        mViewSpacingSpecified = false;
+    }
+
+    /**
+     * Sets a click listener or a message to be sent when the button is clicked.
+     * You only need to pass one of {@code listener} or {@code msg}.
+     *
+     * @param whichButton Which button, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @param msg The {@link Message} to be sent when clicked.
+     */
+    public void setButton(int whichButton, CharSequence text,
+            DialogInterface.OnClickListener listener, Message msg) {
+
+        if (msg == null && listener != null) {
+            msg = mHandler.obtainMessage(whichButton, listener);
+        }
+
+        switch (whichButton) {
+
+            case DialogInterface.BUTTON_POSITIVE:
+                mButtonPositiveText = text;
+                mButtonPositiveMessage = msg;
+                break;
+
+            case DialogInterface.BUTTON_NEGATIVE:
+                mButtonNegativeText = text;
+                mButtonNegativeMessage = msg;
+                break;
+
+            case DialogInterface.BUTTON_NEUTRAL:
+                mButtonNeutralText = text;
+                mButtonNeutralMessage = msg;
+                break;
+
+            default:
+                throw new IllegalArgumentException("Button does not exist");
+        }
+    }
+
+    /**
+     * Specifies the icon to display next to the alert title.
+     *
+     * @param resId the resource identifier of the drawable to use as the icon,
+     *            or 0 for no icon
+     */
+    public void setIcon(int resId) {
+        mIcon = null;
+        mIconId = resId;
+
+        if (mIconView != null) {
+            if (resId != 0) {
+                mIconView.setVisibility(View.VISIBLE);
+                mIconView.setImageResource(mIconId);
+            } else {
+                mIconView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    /**
+     * Specifies the icon to display next to the alert title.
+     *
+     * @param icon the drawable to use as the icon or null for no icon
+     */
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+        mIconId = 0;
+
+        if (mIconView != null) {
+            if (icon != null) {
+                mIconView.setVisibility(View.VISIBLE);
+                mIconView.setImageDrawable(icon);
+            } else {
+                mIconView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    public Button getButton(int whichButton) {
+        switch (whichButton) {
+            case DialogInterface.BUTTON_POSITIVE:
+                return mButtonPositive;
+            case DialogInterface.BUTTON_NEGATIVE:
+                return mButtonNegative;
+            case DialogInterface.BUTTON_NEUTRAL:
+                return mButtonNeutral;
+            default:
+                return null;
+        }
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mScrollView != null && mScrollView.executeKeyEvent(event);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return mScrollView != null && mScrollView.executeKeyEvent(event);
+    }
+
+    /**
+     * Resolves whether a custom or default panel should be used. Removes the
+     * default panel if a custom panel should be used. If the resolved panel is
+     * a view stub, inflates before returning.
+     *
+     * @param customPanel the custom panel
+     * @param defaultPanel the default panel
+     * @return the panel to use
+     */
+    @Nullable
+    private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
+        if (customPanel == null) {
+            // Inflate the default panel, if needed.
+            if (defaultPanel instanceof ViewStub) {
+                defaultPanel = ((ViewStub) defaultPanel).inflate();
+            }
+
+            return (ViewGroup) defaultPanel;
+        }
+
+        // Remove the default panel entirely.
+        if (defaultPanel != null) {
+            final ViewParent parent = defaultPanel.getParent();
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).removeView(defaultPanel);
+            }
+        }
+
+        // Inflate the custom panel, if needed.
+        if (customPanel instanceof ViewStub) {
+            customPanel = ((ViewStub) customPanel).inflate();
+        }
+
+        return (ViewGroup) customPanel;
+    }
+
+    private void setupView() {
+        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
+        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
+        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
+        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
+
+        // Install custom content before setting up the title or buttons so
+        // that we can handle panel overrides.
+        final ViewGroup customPanel = parentPanel.findViewById(R.id.customPanel);
+        setupCustomContent(customPanel);
+
+        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
+        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
+        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
+
+        // Resolve the correct panels and remove the defaults, if needed.
+        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
+        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
+        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
+
+        setupContent(contentPanel);
+        setupButtons(buttonPanel);
+        setupTitle(topPanel);
+
+        final boolean hasCustomPanel = customPanel != null
+                && customPanel.getVisibility() != View.GONE;
+        final boolean hasTopPanel = topPanel != null
+                && topPanel.getVisibility() != View.GONE;
+        final boolean hasButtonPanel = buttonPanel != null
+                && buttonPanel.getVisibility() != View.GONE;
+
+        if (!parentPanel.isInTouchMode()) {
+            final View content = hasCustomPanel ? customPanel : contentPanel;
+            if (!requestFocusForContent(content)) {
+                requestFocusForDefaultButton();
+            }
+        }
+
+        if (hasTopPanel) {
+            // Only clip scrolling content to padding if we have a title.
+            if (mScrollView != null) {
+                mScrollView.setClipToPadding(true);
+            }
+
+            // Only show the divider if we have a title.
+            View divider = null;
+            if (mMessage != null || hasCustomPanel) {
+                divider = topPanel.findViewById(R.id.titleDividerNoCustom);
+            }
+
+            if (divider != null) {
+                divider.setVisibility(View.VISIBLE);
+            }
+        } else {
+            if (contentPanel != null) {
+                final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
+                if (spacer != null) {
+                    spacer.setVisibility(View.VISIBLE);
+                }
+            }
+        }
+
+        // Update scroll indicators as needed.
+        if (!hasCustomPanel) {
+            final View content = mScrollView;
+            if (content != null) {
+                final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
+                        | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
+                content.setScrollIndicators(indicators,
+                        View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+            }
+        }
+
+        final TypedArray a = mContext.obtainStyledAttributes(
+                null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+        setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
+                hasTopPanel, hasCustomPanel, hasButtonPanel);
+        a.recycle();
+    }
+
+    private boolean requestFocusForContent(View content) {
+        return content != null && content.requestFocus();
+    }
+
+    private void requestFocusForDefaultButton() {
+        if (mButtonPositive.getVisibility() == View.VISIBLE) {
+            mButtonPositive.requestFocus();
+        } else if (mButtonNegative.getVisibility() == View.VISIBLE) {
+            mButtonNegative.requestFocus();
+        } else if (mButtonNeutral.getVisibility() == View.VISIBLE) {
+            mButtonNeutral.requestFocus();
+        }
+    }
+
+    private void setupCustomContent(ViewGroup customPanel) {
+        final View customView;
+        if (mView != null) {
+            customView = mView;
+        } else if (mViewLayoutResId != 0) {
+            final LayoutInflater inflater = LayoutInflater.from(mContext);
+            customView = inflater.inflate(mViewLayoutResId, customPanel, false);
+        } else {
+            customView = null;
+        }
+
+        final boolean hasCustomView = customView != null;
+        if (!hasCustomView || !canTextInput(customView)) {
+            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        }
+
+        if (hasCustomView) {
+            final FrameLayout custom = mWindow.findViewById(R.id.custom);
+            custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+            if (mViewSpacingSpecified) {
+                custom.setPadding(
+                        mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
+            }
+        } else {
+            customPanel.setVisibility(View.GONE);
+        }
+    }
+
+    private void setupTitle(ViewGroup topPanel) {
+        mIconView = mWindow.findViewById(R.id.icon);
+
+        final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
+        if (hasTextTitle && mShowTitle) {
+            // Display the title if a title is supplied, else hide it.
+            mTitleView = mWindow.findViewById(R.id.alertTitle);
+            mTitleView.setText(mTitle);
+
+            // Do this last so that if the user has supplied any icons we
+            // use them instead of the default ones. If the user has
+            // specified 0 then make it disappear.
+            if (mIconId != 0) {
+                mIconView.setImageResource(mIconId);
+            } else if (mIcon != null) {
+                mIconView.setImageDrawable(mIcon);
+            } else {
+                // Apply the padding from the icon to ensure the title is
+                // aligned correctly.
+                mTitleView.setPadding(mIconView.getPaddingLeft(),
+                        mIconView.getPaddingTop(),
+                        mIconView.getPaddingRight(),
+                        mIconView.getPaddingBottom());
+                mIconView.setVisibility(View.GONE);
+            }
+        } else {
+            // Hide the title template
+            final View titleTemplate = mWindow.findViewById(R.id.title_template);
+            titleTemplate.setVisibility(View.GONE);
+            mIconView.setVisibility(View.GONE);
+            topPanel.setVisibility(View.GONE);
+        }
+    }
+
+    private void setupContent(ViewGroup contentPanel) {
+        mScrollView = contentPanel.findViewById(R.id.scrollView);
+        mScrollView.setFocusable(false);
+
+        // Special case for users that only want to display a String
+        mMessageView = contentPanel.findViewById(R.id.message);
+        if (mMessageView == null) {
+            return;
+        }
+
+        mMessageView.setVisibility(View.GONE);
+        mScrollView.removeView(mMessageView);
+
+        contentPanel.setVisibility(View.GONE);
+    }
+
+    private void setupButtons(ViewGroup buttonPanel) {
+        int BIT_BUTTON_POSITIVE = 1;
+        int BIT_BUTTON_NEGATIVE = 2;
+        int BIT_BUTTON_NEUTRAL = 4;
+        int whichButtons = 0;
+        mButtonPositive = buttonPanel.findViewById(R.id.button1);
+        mButtonPositive.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonPositiveText)) {
+            mButtonPositive.setVisibility(View.GONE);
+        } else {
+            mButtonPositive.setText(mButtonPositiveText);
+            mButtonPositive.setVisibility(View.VISIBLE);
+            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
+        }
+
+        mButtonNegative = buttonPanel.findViewById(R.id.button2);
+        mButtonNegative.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonNegativeText)) {
+            mButtonNegative.setVisibility(View.GONE);
+        } else {
+            mButtonNegative.setText(mButtonNegativeText);
+            mButtonNegative.setVisibility(View.VISIBLE);
+
+            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
+        }
+
+        mButtonNeutral = buttonPanel.findViewById(R.id.button3);
+        mButtonNeutral.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonNeutralText)) {
+            mButtonNeutral.setVisibility(View.GONE);
+        } else {
+            mButtonNeutral.setText(mButtonNeutralText);
+            mButtonNeutral.setVisibility(View.VISIBLE);
+
+            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
+        }
+
+        final boolean hasButtons = whichButtons != 0;
+        if (!hasButtons) {
+            buttonPanel.setVisibility(View.GONE);
+        }
+    }
+
+    private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
+            View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
+        int fullDark = 0;
+        int topDark = 0;
+        int centerDark = 0;
+        int bottomDark = 0;
+        int fullBright = 0;
+        int topBright = 0;
+        int centerBright = 0;
+        int bottomBright = 0;
+        int bottomMedium = 0;
+
+        topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
+        topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
+        centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
+        centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
+
+        /* We now set the background of all of the sections of the alert.
+         * First collect together each section that is being displayed along
+         * with whether it is on a light or dark background, then run through
+         * them setting their backgrounds.  This is complicated because we need
+         * to correctly use the full, top, middle, and bottom graphics depending
+         * on how many views they are and where they appear.
+         */
+
+        final View[] views = new View[4];
+        final boolean[] light = new boolean[4];
+        View lastView = null;
+        boolean lastLight = false;
+
+        int pos = 0;
+        if (hasTitle) {
+            views[pos] = topPanel;
+            light[pos] = false;
+            pos++;
+        }
+
+        /* The contentPanel displays either a custom text message or
+         * a ListView. If it's text we should use the dark background
+         * for ListView we should use the light background. PIA does not use
+         * a list view. Hence, we set it to use dark background.  If neither
+         * are there the contentPanel will be hidden so set it as null.
+         */
+        views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
+        light[pos] = false;
+        pos++;
+
+        if (hasCustomView) {
+            views[pos] = customPanel;
+            light[pos] = false;
+            pos++;
+        }
+
+        if (hasButtons) {
+            views[pos] = buttonPanel;
+            light[pos] = true;
+        }
+
+        boolean setView = false;
+        for (pos = 0; pos < views.length; pos++) {
+            final View v = views[pos];
+            if (v == null) {
+                continue;
+            }
+
+            if (lastView != null) {
+                if (!setView) {
+                    lastView.setBackgroundResource(lastLight ? topBright : topDark);
+                } else {
+                    lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
+                }
+                setView = true;
+            }
+
+            lastView = v;
+            lastLight = light[pos];
+        }
+
+        if (lastView != null) {
+            if (setView) {
+                bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
+                bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
+                bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
+
+                // ListViews will use the Bright background, but buttons use the
+                // Medium background.
+                lastView.setBackgroundResource(
+                        lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
+            } else {
+                fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
+                fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
+
+                lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
+            }
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java
new file mode 100644
index 0000000..e22171e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+
+import com.android.packageinstaller.R;
+
+/**
+ * Special implementation of linear layout that's capable of laying out alert
+ * dialog components.
+ * <p>
+ * A dialog consists of up to three panels. All panels are optional, and a
+ * dialog may contain only a single panel. The panels are laid out according
+ * to the following guidelines:
+ * <ul>
+ *     <li>topPanel: exactly wrap_content</li>
+ *     <li>contentPanel OR customPanel: at most fill_parent, first priority for
+ *         extra space</li>
+ *     <li>buttonPanel: at least minHeight, at most wrap_content, second
+ *         priority for extra space</li>
+ * </ul>
+ */
+public class AlertDialogLayout extends LinearLayout {
+
+    public AlertDialogLayout(@Nullable Context context) {
+        super(context);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!tryOnMeasure(widthMeasureSpec, heightMeasureSpec)) {
+            // Failed to perform custom measurement, let superclass handle it.
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    private boolean tryOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        View topPanel = null;
+        View buttonPanel = null;
+        View middlePanel = null;
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+
+            final int id = child.getId();
+            switch (id) {
+                case R.id.topPanel:
+                    topPanel = child;
+                    break;
+                case R.id.buttonPanel:
+                    buttonPanel = child;
+                    break;
+                case R.id.contentPanel:
+                case R.id.customPanel:
+                    if (middlePanel != null) {
+                        // Both the content and custom are visible. Abort!
+                        return false;
+                    }
+                    middlePanel = child;
+                    break;
+                default:
+                    // Unknown top-level child. Abort!
+                    return false;
+            }
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+        int childState = 0;
+        int usedHeight = getPaddingTop() + getPaddingBottom();
+
+        if (topPanel != null) {
+            topPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+
+            usedHeight += topPanel.getMeasuredHeight();
+            childState = combineMeasuredStates(childState, topPanel.getMeasuredState());
+        }
+
+        int buttonHeight = 0;
+        int buttonWantsHeight = 0;
+        if (buttonPanel != null) {
+            buttonPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+            buttonHeight = resolveMinimumHeight(buttonPanel);
+            buttonWantsHeight = buttonPanel.getMeasuredHeight() - buttonHeight;
+
+            usedHeight += buttonHeight;
+            childState = combineMeasuredStates(childState, buttonPanel.getMeasuredState());
+        }
+
+        int middleHeight = 0;
+        if (middlePanel != null) {
+            final int childHeightSpec;
+            if (heightMode == MeasureSpec.UNSPECIFIED) {
+                childHeightSpec = MeasureSpec.UNSPECIFIED;
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(
+                        Math.max(0, heightSize - usedHeight), heightMode);
+            }
+
+            middlePanel.measure(widthMeasureSpec, childHeightSpec);
+            middleHeight = middlePanel.getMeasuredHeight();
+
+            usedHeight += middleHeight;
+            childState = combineMeasuredStates(childState, middlePanel.getMeasuredState());
+        }
+
+        int remainingHeight = heightSize - usedHeight;
+
+        // Time for the "real" button measure pass. If we have remaining space,
+        // make the button pane bigger up to its target height. Otherwise,
+        // just remeasure the button at whatever height it needs.
+        if (buttonPanel != null) {
+            usedHeight -= buttonHeight;
+
+            final int heightToGive = Math.min(remainingHeight, buttonWantsHeight);
+            if (heightToGive > 0) {
+                remainingHeight -= heightToGive;
+                buttonHeight += heightToGive;
+            }
+
+            final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    buttonHeight, MeasureSpec.EXACTLY);
+            buttonPanel.measure(widthMeasureSpec, childHeightSpec);
+
+            usedHeight += buttonPanel.getMeasuredHeight();
+            childState = combineMeasuredStates(childState, buttonPanel.getMeasuredState());
+        }
+
+        // If we still have remaining space, make the middle pane bigger up
+        // to the maximum height.
+        if (middlePanel != null && remainingHeight > 0) {
+            usedHeight -= middleHeight;
+
+            final int heightToGive = remainingHeight;
+            remainingHeight -= heightToGive;
+            middleHeight += heightToGive;
+
+            // Pass the same height mode as we're using for the dialog itself.
+            // If it's EXACTLY, then the middle pane MUST use the entire
+            // height.
+            final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    middleHeight, heightMode);
+            middlePanel.measure(widthMeasureSpec, childHeightSpec);
+
+            usedHeight += middlePanel.getMeasuredHeight();
+            childState = combineMeasuredStates(childState, middlePanel.getMeasuredState());
+        }
+
+        // Compute desired width as maximum child width.
+        int maxWidth = 0;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != View.GONE) {
+                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+            }
+        }
+
+        maxWidth += getPaddingLeft() + getPaddingRight();
+
+        final int widthSizeAndState = resolveSizeAndState(maxWidth, widthMeasureSpec, childState);
+        final int heightSizeAndState = resolveSizeAndState(usedHeight, heightMeasureSpec, 0);
+        setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+
+        // If the children weren't already measured EXACTLY, we need to run
+        // another measure pass to for MATCH_PARENT widths.
+        if (widthMode != MeasureSpec.EXACTLY) {
+            forceUniformWidth(count, heightMeasureSpec);
+        }
+
+        return true;
+    }
+
+    /**
+     * Remeasures child views to exactly match the layout's measured width.
+     *
+     * @param count the number of child views
+     * @param heightMeasureSpec the original height measure spec
+     */
+    private void forceUniformWidth(int count, int heightMeasureSpec) {
+        // Pretend that the linear layout has an exact size.
+        final int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(
+                getMeasuredWidth(), MeasureSpec.EXACTLY);
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    // Temporarily force children to reuse their old measured
+                    // height.
+                    final int oldHeight = lp.height;
+                    lp.height = child.getMeasuredHeight();
+
+                    // Remeasure with new dimensions.
+                    measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+                    lp.height = oldHeight;
+                }
+            }
+        }
+    }
+
+    /**
+     * Attempts to resolve the minimum height of a view.
+     * <p>
+     * If the view doesn't have a minimum height set and only contains a single
+     * child, attempts to resolve the minimum height of the child view.
+     *
+     * @param v the view whose minimum height to resolve
+     * @return the minimum height
+     */
+    private int resolveMinimumHeight(View v) {
+        final int minHeight = v.getMinimumHeight();
+        if (minHeight > 0) {
+            return minHeight;
+        }
+
+        if (v instanceof ViewGroup) {
+            final ViewGroup vg = (ViewGroup) v;
+            if (vg.getChildCount() == 1) {
+                return resolveMinimumHeight(vg.getChildAt(0));
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int paddingLeft = getPaddingLeft();
+        final int paddingRight = getPaddingRight();
+        final int paddingTop = getPaddingTop();
+
+        // Where right end of child should go
+        final int width = right - left;
+        final int childRight = width - paddingRight;
+
+        // Space available for child
+        final int childSpace = width - paddingLeft - paddingRight;
+
+        final int totalLength = getMeasuredHeight();
+        final int count = getChildCount();
+        final int gravity = getGravity();
+        final int majorGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int minorGravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+        int childTop;
+        switch (majorGravity) {
+            case Gravity.BOTTOM:
+                // totalLength contains the padding already
+                childTop = paddingTop + bottom - top - totalLength;
+                break;
+
+            // totalLength contains the padding already
+            case Gravity.CENTER_VERTICAL:
+                childTop = paddingTop + (bottom - top - totalLength) / 2;
+                break;
+
+            case Gravity.TOP:
+            default:
+                childTop = paddingTop;
+                break;
+        }
+
+        final Drawable dividerDrawable = getDividerDrawable();
+        final int dividerHeight = dividerDrawable == null ?
+                0 : dividerDrawable.getIntrinsicHeight();
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+
+                final LayoutParams lp =
+                        (LayoutParams) child.getLayoutParams();
+
+                int layoutGravity = lp.gravity;
+                if (layoutGravity < 0) {
+                    layoutGravity = minorGravity;
+                }
+                final int layoutDirection = getLayoutDirection();
+                final int absoluteGravity = Gravity.getAbsoluteGravity(
+                        layoutGravity, layoutDirection);
+
+                final int childLeft;
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+                                + lp.leftMargin - lp.rightMargin;
+                        break;
+
+                    case Gravity.RIGHT:
+                        childLeft = childRight - childWidth - lp.rightMargin;
+                        break;
+
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = paddingLeft + lp.leftMargin;
+                        break;
+                }
+
+                childTop += lp.topMargin;
+                setChildFrame(child, childLeft, childTop, childWidth, childHeight);
+                childTop += childHeight + lp.bottomMargin;
+            }
+        }
+    }
+
+    private void setChildFrame(View child, int left, int top, int width, int height) {
+        child.layout(left, top, left + width, top + height);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java b/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java
new file mode 100644
index 0000000..8d478c4
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.packageinstaller.R;
+
+/**
+ * An extension of LinearLayout that automatically switches to vertical
+ * orientation when it can't fit its child views horizontally.
+ */
+public class ButtonBarLayout extends LinearLayout {
+    /** Amount of the second button to "peek" above the fold when stacked. */
+    private static final int PEEK_BUTTON_DP = 16;
+
+    /** Whether the current configuration allows stacking. */
+    private boolean mAllowStacking;
+
+    private int mLastWidthSize = -1;
+
+    private int mMinimumHeight = 0;
+
+    public ButtonBarLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
+        ta.recycle();
+    }
+
+    public void setAllowStacking(boolean allowStacking) {
+        if (mAllowStacking != allowStacking) {
+            mAllowStacking = allowStacking;
+            if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
+                setStacked(false);
+            }
+            requestLayout();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        if (mAllowStacking) {
+            if (widthSize > mLastWidthSize && isStacked()) {
+                // We're being measured wider this time, try un-stacking.
+                setStacked(false);
+            }
+
+            mLastWidthSize = widthSize;
+        }
+
+        boolean needsRemeasure = false;
+
+        // If we're not stacked, make sure the measure spec is AT_MOST rather
+        // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
+        // know to stack the buttons.
+        final int initialWidthMeasureSpec;
+        if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+            initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
+            // We'll need to remeasure again to fill excess space.
+            needsRemeasure = true;
+        } else {
+            initialWidthMeasureSpec = widthMeasureSpec;
+        }
+
+        super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+
+        if (mAllowStacking && !isStacked()) {
+            final int measuredWidth = getMeasuredWidthAndState();
+            final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
+            if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+                setStacked(true);
+
+                // Measure again in the new orientation.
+                needsRemeasure = true;
+            }
+        }
+
+        if (needsRemeasure) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        // Compute minimum height such that, when stacked, some portion of the
+        // second button is visible.
+        int minHeight = 0;
+        final int firstVisible = getNextVisibleChildIndex(0);
+        if (firstVisible >= 0) {
+            final View firstButton = getChildAt(firstVisible);
+            final LayoutParams firstParams = (LayoutParams) firstButton.getLayoutParams();
+            minHeight += getPaddingTop() + firstButton.getMeasuredHeight()
+                    + firstParams.topMargin + firstParams.bottomMargin;
+            if (isStacked()) {
+                final int secondVisible = getNextVisibleChildIndex(firstVisible + 1);
+                if (secondVisible >= 0) {
+                    minHeight += getChildAt(secondVisible).getPaddingTop()
+                            + PEEK_BUTTON_DP * getResources().getDisplayMetrics().density;
+                }
+            } else {
+                minHeight += getPaddingBottom();
+            }
+        }
+
+        if (getMinimumHeight() != minHeight) {
+            setMinimumHeight(minHeight);
+        }
+    }
+
+    private int getNextVisibleChildIndex(int index) {
+        for (int i = index, count = getChildCount(); i < count; i++) {
+            if (getChildAt(i).getVisibility() == View.VISIBLE) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int getMinimumHeight() {
+        return Math.max(mMinimumHeight, super.getMinimumHeight());
+    }
+
+    private void setStacked(boolean stacked) {
+        setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+        setGravity(stacked ? Gravity.END : Gravity.BOTTOM);
+
+        final View spacer = findViewById(R.id.spacer);
+        if (spacer != null) {
+            spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
+        }
+
+        // Reverse the child order. This is specific to the Material button
+        // bar's layout XML and will probably not generalize.
+        final int childCount = getChildCount();
+        for (int i = childCount - 2; i >= 0; i--) {
+            bringChildToFront(getChildAt(i));
+        }
+    }
+
+    private boolean isStacked() {
+        return getOrientation() == LinearLayout.VERTICAL;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 33e5231..4c5875b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,11 +16,12 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
+
 import java.io.File;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java b/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java
new file mode 100644
index 0000000..068834c
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java
@@ -0,0 +1,78 @@
+/* 
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.packageinstaller;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+/**
+ * Used by dialogs to change the font size and number of lines to try to fit
+ * the text to the available space.
+ */
+public class DialogTitle extends TextView {
+
+    public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public DialogTitle(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DialogTitle(Context context) {
+        super(context);
+    }
+    
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final Layout layout = getLayout();
+        if (layout != null) {
+            final int lineCount = layout.getLineCount();
+            if (lineCount > 0) {
+                final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
+                if (ellipsisCount > 0) {
+                    setSingleLine(false);
+                    setMaxLines(2);
+
+                    final TypedArray a = getContext().obtainStyledAttributes(null,
+                            R.styleable.TextAppearance, android.R.attr.textAppearanceMedium,
+                            android.R.style.TextAppearance_Medium);
+                    final int textSize = a.getDimensionPixelSize(
+                            R.styleable.TextAppearance_textSize, 0);
+                    if (textSize != 0) {
+                        // textSize is already expressed in pixels
+                        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+                    }
+                    a.recycle();
+
+                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);      
+                }
+            }
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
index 3a94fdc..8639f47 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
@@ -16,8 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
@@ -27,6 +25,9 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -40,17 +41,18 @@
 /**
  * Persists results of events and calls back observers when a matching result arrives.
  */
-class EventResultPersister {
+public class EventResultPersister {
     private static final String LOG_TAG = EventResultPersister.class.getSimpleName();
 
     /** Id passed to {@link #addObserver(int, EventResultObserver)} to generate new id */
-    static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
+    public static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
 
     /**
      * The extra with the id to set in the intent delivered to
      * {@link #onEventReceived(Context, Intent)}
      */
-    static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+    public static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+    public static final String EXTRA_SERVICE_ID = "EventResultPersister.EXTRA_SERVICE_ID";
 
     /** Persisted state of this object */
     private final AtomicFile mResultsFile;
@@ -89,8 +91,8 @@
     }
 
     /** Call back when a result is received. Observer is removed when onResult it called. */
-    interface EventResultObserver {
-        void onResult(int status, int legacyStatus, @Nullable String message);
+    public interface EventResultObserver {
+        void onResult(int status, int legacyStatus, @Nullable String message, int serviceId);
     }
 
     /**
@@ -153,12 +155,14 @@
                     int status = readIntAttribute(parser, "status");
                     int legacyStatus = readIntAttribute(parser, "legacyStatus");
                     String statusMessage = readStringAttribute(parser, "statusMessage");
+                    int serviceId = readIntAttribute(parser, "serviceId");
 
                     if (mResults.get(id) != null) {
                         throw new Exception("id " + id + " has two results");
                     }
 
-                    mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
+                    mResults.put(id, new EventResult(status, legacyStatus, statusMessage,
+                            serviceId));
                 } else {
                     throw new Exception("unexpected tag");
                 }
@@ -190,6 +194,7 @@
         int id = intent.getIntExtra(EXTRA_ID, 0);
         String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
         int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
+        int serviceId = intent.getIntExtra(EXTRA_SERVICE_ID, 0);
 
         EventResultObserver observerToCall = null;
         synchronized (mLock) {
@@ -204,9 +209,9 @@
             }
 
             if (observerToCall != null) {
-                observerToCall.onResult(status, legacyStatus, statusMessage);
+                observerToCall.onResult(status, legacyStatus, statusMessage, serviceId);
             } else {
-                mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
+                mResults.put(id, new EventResult(status, legacyStatus, statusMessage, serviceId));
                 writeState();
             }
         }
@@ -258,6 +263,8 @@
                                     serializer.attribute(null, "statusMessage",
                                             results.valueAt(i).message);
                                 }
+                                serializer.attribute(null, "serviceId",
+                                        Integer.toString(results.valueAt(i).serviceId));
                                 serializer.endTag(null, "result");
                             }
 
@@ -311,7 +318,8 @@
             if (resultIndex >= 0) {
                 EventResult result = mResults.valueAt(resultIndex);
 
-                observer.onResult(result.status, result.legacyStatus, result.message);
+                observer.onResult(result.status, result.legacyStatus, result.message,
+                        result.serviceId);
                 mResults.removeAt(resultIndex);
                 writeState();
             } else {
@@ -341,13 +349,15 @@
         public final int status;
         public final int legacyStatus;
         @Nullable public final String message;
+        public final int serviceId;
 
-        private EventResult(int status, int legacyStatus, @Nullable String message) {
+        private EventResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
             this.status = status;
             this.legacyStatus = legacyStatus;
             this.message = message;
+            this.serviceId = serviceId;
         }
     }
 
-    class OutOfIdsException extends Exception {}
+    public class OutOfIdsException extends Exception {}
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
index c70d7db..be8eabb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
@@ -16,11 +16,12 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.annotation.NonNull;
+
 /**
  * Receives install events and perists them using a {@link EventResultPersister}.
  */
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index 54105bb..3505cfb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -32,7 +31,7 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 
@@ -79,6 +78,8 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        setFinishOnTouchOutside(true);
+
         int statusCode = getIntent().getIntExtra(PackageInstaller.EXTRA_STATUS,
                 PackageInstaller.STATUS_FAILURE);
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 3aa8dbf..93387e2 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,28 +16,22 @@
 
 package com.android.packageinstaller;
 
-import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
-
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.parsing.ApkLiteParseUtils;
-import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 
-import com.android.internal.app.AlertActivity;
-import com.android.internal.content.InstallLocationUtils;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -139,34 +133,30 @@
                 params.setOriginatingUri(getIntent()
                         .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
                 params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                        UID_UNKNOWN));
+                        Process.INVALID_UID));
                 params.setInstallerPackageName(getIntent().getStringExtra(
                         Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                 params.setInstallReason(PackageManager.INSTALL_REASON_USER);
 
                 File file = new File(mPackageURI.getPath());
                 try {
-                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-                    final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
-                            input.reset(), file, /* flags */ 0);
-                    if (result.isError()) {
-                        Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
-                        Log.e(LOG_TAG,
-                                "Cannot calculate installed size " + file + ". Try only apk size.");
+                    final InstallInfo result = getPackageManager().getPackageInstaller()
+                            .getInstallInfo(file, 0);
+                    params.setAppPackageName(result.getPackageName());
+                    params.setInstallLocation(result.getInstallLocation());
+                    try {
+                        params.setSize(result.calculateInstalledSize(params));
+                    } catch (IOException e) {
+                        e.printStackTrace();
                         params.setSize(file.length());
-                    } else {
-                        final PackageLite pkg = result.getResult();
-                        params.setAppPackageName(pkg.getPackageName());
-                        params.setInstallLocation(pkg.getInstallLocation());
-                        params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
-                                params.abiOverride));
                     }
-                } catch (IOException e) {
+                } catch (PackageInstaller.PackageParsingException e) {
+
+                    Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e);
                     Log.e(LOG_TAG,
                             "Cannot calculate installed size " + file + ". Try only apk size.");
                     params.setSize(file.length());
                 }
-
                 try {
                     mInstallId = InstallEventReceiver
                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
@@ -281,8 +271,11 @@
      * @param statusCode    The installation result.
      * @param legacyStatus  The installation as used internally in the package manager.
      * @param statusMessage The detailed installation result.
+     * @param serviceId     Id for PowerManager.WakeLock service. Used only by Wear devices
+     *                      during an uninstall.
      */
-    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
+    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage,
+            int serviceId /* ignore */) {
         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
             launchSuccess();
         } else {
@@ -292,7 +285,7 @@
 
     /**
      * Send the package to the package installer and then register a event result observer that
-     * will call {@link #launchFinishBasedOnResult(int, int, String)}
+     * will call {@link #launchFinishBasedOnResult(int, int, String, int)}
      */
     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
             PackageInstaller.Session> {
@@ -318,6 +311,7 @@
 
                 try (InputStream in = new FileInputStream(file)) {
                     long sizeBytes = file.length();
+                    long totalRead = 0;
                     try (OutputStream out = session
                             .openWrite("PackageInstaller", 0, sizeBytes)) {
                         byte[] buffer = new byte[1024 * 1024];
@@ -336,8 +330,9 @@
 
                             out.write(buffer, 0, numRead);
                             if (sizeBytes > 0) {
-                                float fraction = ((float) numRead / (float) sizeBytes);
-                                session.addProgress(fraction);
+                                totalRead += numRead;
+                                float fraction = ((float) totalRead / (float) sizeBytes);
+                                session.setStagingProgress(fraction);
                             }
                         }
                     }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index b6b8837..68de2f6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -31,7 +30,7 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -58,6 +57,7 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        setFinishOnTouchOutside(true);
         mAlert.setIcon(R.drawable.ic_file_download);
         mAlert.setTitle(getString(R.string.app_name_unknown));
         mAlert.setView(R.layout.install_content_view);
@@ -123,7 +123,8 @@
      * Show an error message and set result as error.
      */
     private void showError() {
-        (new ErrorDialog()).showAllowingStateLoss(getFragmentManager(), "error");
+        getFragmentManager().beginTransaction()
+                .add(new ErrorDialog(), "error").commitAllowingStateLoss();
 
         Intent result = new Intent();
         result.putExtra(Intent.EXTRA_INSTALL_RESULT,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 88c1036..7338e64 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -19,26 +19,25 @@
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.RemoteException;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.Arrays;
 
 /**
@@ -80,12 +79,11 @@
         final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
         final int originatingUid = getOriginatingUid(sourceInfo);
         boolean isTrustedSource = false;
-        if (sourceInfo != null
-                && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+        if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
             isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
         }
 
-        if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
+        if (!isTrustedSource && originatingUid != Process.INVALID_UID) {
             final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
             if (targetSdkVersion < 0) {
                 Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
@@ -99,6 +97,10 @@
             }
         }
 
+        if (sessionId != -1 && !isCallerSessionOwner(originatingUid, sessionId)) {
+            mAbortInstall = true;
+        }
+
         final String installerPackageNameFromIntent = getIntent().getStringExtra(
                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
         if (installerPackageNameFromIntent != null) {
@@ -208,30 +210,27 @@
     }
 
     /**
-     * Get the originating uid if possible, or
-     * {@link android.content.pm.PackageInstaller.SessionParams#UID_UNKNOWN} if not available
+     * Get the originating uid if possible, or {@link Process#INVALID_UID} if not available
      *
      * @param sourceInfo The source of this installation
-     * @return The UID of the installation source or UID_UNKNOWN
+     * @return The UID of the installation source or INVALID_UID
      */
     private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
         // The originating uid from the intent. We only trust/use this if it comes from either
         // the document manager app or the downloads provider
         final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                PackageInstaller.SessionParams.UID_UNKNOWN);
+                Process.INVALID_UID);
 
         final int callingUid;
         if (sourceInfo != null) {
             callingUid = sourceInfo.uid;
         } else {
-            try {
-                callingUid = ActivityManager.getService()
-                        .getLaunchedFromUid(getActivityToken());
-            } catch (RemoteException ex) {
+            callingUid = getLaunchedFromUid();
+            if (callingUid == Process.INVALID_UID) {
                 // Cannot reach ActivityManager. Aborting install.
                 Log.e(LOG_TAG, "Could not determine the launching uid.");
                 mAbortInstall = true;
-                return PackageInstaller.SessionParams.UID_UNKNOWN;
+                return Process.INVALID_UID;
             }
         }
         if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
@@ -253,7 +252,8 @@
             return false;
         }
         final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
-        return (appInfo.isSystemApp() && uid == appInfo.uid);
+        return ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                && uid == appInfo.uid);
     }
 
     @NonNull
@@ -268,8 +268,14 @@
 
         try {
             return mPackageManager.canPackageQuery(callingPackage, targetPackage);
-        } catch (NameNotFoundException e) {
+        } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
     }
+
+    private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
+        PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+        int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid();
+        return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid);
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 38c06dd..73c03a5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
@@ -30,7 +29,7 @@
 import android.view.View;
 import android.widget.Button;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.util.List;
@@ -54,6 +53,8 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        setFinishOnTouchOutside(true);
+
         if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
             // Return result if requested
             Intent result = new Intent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
index eea12ec..228a9f5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -24,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -32,8 +30,11 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 /**
  * A util class that handle and post new app installed notifications.
  */
@@ -107,8 +108,8 @@
             @NonNull String packageName) {
         CharSequence label = appInfo.loadSafeLabel(context.getPackageManager(),
                 DEFAULT_MAX_LABEL_SIZE_PX,
-                PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                        | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE).toString();
+                TextUtils.SAFE_STRING_FLAG_TRIM
+                        | TextUtils.SAFE_STRING_FLAG_FIRST_LINE).toString();
         if (label != null) {
             return label.toString();
         }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
index 74c7b58..2278f7c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.provider.Settings;
 import android.util.Log;
 
 /**
@@ -33,8 +32,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 0) {
+        if (!context.getPackageManager().shouldShowNewAppInstalledNotification()) {
             return;
         }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index fa93670..3138158 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -21,11 +21,8 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -36,24 +33,26 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -86,13 +85,12 @@
     private Uri mPackageURI;
     private Uri mOriginatingURI;
     private Uri mReferrerURI;
-    private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;
+    private int mOriginatingUid = Process.INVALID_UID;
     private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
     private int mActivityResultCode = Activity.RESULT_CANCELED;
 
     private final boolean mLocalLOGV = false;
     PackageManager mPm;
-    IPackageManager mIpm;
     AppOpsManager mAppOpsManager;
     UserManager mUserManager;
     PackageInstaller mInstaller;
@@ -166,7 +164,8 @@
 
         DialogFragment newDialog = createDialog(id);
         if (newDialog != null) {
-            newDialog.showAllowingStateLoss(getFragmentManager(), "dialog");
+            getFragmentManager().beginTransaction()
+                    .add(newDialog, "dialog").commitAllowingStateLoss();
         }
     }
 
@@ -211,9 +210,9 @@
             // Log the fact that the app is requesting an install, and is now allowed to do it
             // (before this point we could only log that it's requesting an install, but isn't
             // allowed to do it yet).
-            int appOpCode =
-                    AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
-            mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage,
+            String appOpStr =
+                    AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
+            mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid, mOriginatingPackage,
                     mCallingAttributionTag,
                     "Successfully started package installation activity");
 
@@ -250,12 +249,9 @@
     private boolean isInstallRequestFromUnknownSource(Intent intent) {
         if (mCallingPackage != null && intent.getBooleanExtra(
                 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
-            if (mSourceInfo != null) {
-                if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
-                        != 0) {
-                    // Privileged apps can bypass unknown sources check if they want.
-                    return false;
-                }
+            if (mSourceInfo != null && mSourceInfo.isPrivilegedApp()) {
+                // Privileged apps can bypass unknown sources check if they want.
+                return false;
             }
         }
         return true;
@@ -305,7 +301,7 @@
 
     @Override
     protected void onCreate(Bundle icicle) {
-        if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
+        if (mLocalLOGV) Log.i(TAG, "creating for user " + UserHandle.myUserId());
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
         super.onCreate(null);
@@ -316,7 +312,6 @@
         setFinishOnTouchOutside(true);
 
         mPm = getPackageManager();
-        mIpm = AppGlobals.getPackageManager();
         mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
         mInstaller = mPm.getPackageInstaller();
         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
@@ -328,8 +323,8 @@
         mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
         mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
         mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                PackageInstaller.SessionParams.UID_UNKNOWN);
-        mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
+                Process.INVALID_UID);
+        mOriginatingPackage = (mOriginatingUid != Process.INVALID_UID)
                 ? getPackageNameForUid(mOriginatingUid) : null;
 
         final Object packageSource;
@@ -337,21 +332,23 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
+            final String resolvedBaseCodePath = intent.getStringExtra(
+                    PackageInstaller.EXTRA_RESOLVED_BASE_PATH);
+            if (info == null || !info.isSealed() || resolvedBaseCodePath == null) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
                 return;
             }
 
             mSessionId = sessionId;
-            packageSource = Uri.fromFile(new File(info.resolvedBaseCodePath));
+            packageSource = Uri.fromFile(new File(resolvedBaseCodePath));
             mOriginatingURI = null;
             mReferrerURI = null;
         } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            if (info == null || !info.isPreapprovalRequested) {
+            if (info == null || !info.getIsPreApprovalRequested()) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
                 return;
@@ -547,15 +544,15 @@
             return;
         }
         // Shouldn't use static constant directly, see b/65534401.
-        final int appOpCode =
-                AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
-        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid,
+        final String appOpStr =
+                AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
+        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid,
                 mOriginatingPackage, mCallingAttributionTag,
                 "Started package installation activity");
         if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
         switch (appOpMode) {
             case AppOpsManager.MODE_DEFAULT:
-                mAppOpsManager.setMode(appOpCode, mOriginatingUid,
+                mAppOpsManager.setMode(appOpStr, mOriginatingUid,
                         mOriginatingPackage, AppOpsManager.MODE_ERRORED);
                 // fall through
             case AppOpsManager.MODE_ERRORED:
@@ -588,8 +585,8 @@
 
         switch (scheme) {
             case SCHEME_PACKAGE: {
-                for (UserInfo info : mUserManager.getUsers()) {
-                    PackageManager pmForUser = createContextAsUser(info.getUserHandle(), 0)
+                for (UserHandle handle : mUserManager.getUserHandles(true)) {
+                    PackageManager pmForUser = createContextAsUser(handle, 0)
                                                 .getPackageManager();
                     try {
                         if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
@@ -645,9 +642,9 @@
      * @return {@code true} iff the installer could be set up
      */
     private boolean processSessionInfo(@NonNull SessionInfo info) {
-        mPkgInfo = generateStubPackageInfo(info.appPackageName);
-        mAppSnippet = new PackageUtil.AppSnippet(info.appLabel,
-                info.appIcon != null ? new BitmapDrawable(getResources(), info.appIcon)
+        mPkgInfo = generateStubPackageInfo(info.getAppPackageName());
+        mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(),
+                info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon())
                         : getPackageManager().getDefaultActivityIcon());
         return true;
     }
@@ -693,7 +690,7 @@
         if (mReferrerURI != null) {
             newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
         }
-        if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
+        if (mOriginatingUid != Process.INVALID_UID) {
             newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
         }
         if (installerPackageName != null) {
@@ -829,7 +826,7 @@
             if (isDestroyed()) {
                 return;
             }
-            getMainThreadHandler().postDelayed(() -> {
+            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                 if (!isDestroyed()) {
                     startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
                 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index f5570df..698159f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -17,14 +17,11 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
@@ -33,6 +30,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.File;
 
 /**
@@ -131,16 +131,15 @@
     public static AppSnippet getAppSnippet(
             Activity pContext, ApplicationInfo appInfo, File sourceFile) {
         final String archiveFilePath = sourceFile.getAbsolutePath();
-        Resources pRes = pContext.getResources();
-        AssetManager assmgr = new AssetManager();
-        assmgr.addAssetPath(archiveFilePath);
-        Resources res = new Resources(assmgr, pRes.getDisplayMetrics(), pRes.getConfiguration());
+        PackageManager pm = pContext.getPackageManager();
+        appInfo.publicSourceDir = archiveFilePath;
+
         CharSequence label = null;
         // Try to load the label from the package's resources. If an app has not explicitly
         // specified any label, just use the package name.
         if (appInfo.labelRes != 0) {
             try {
-                label = res.getText(appInfo.labelRes);
+                label = appInfo.loadLabel(pm);
             } catch (Resources.NotFoundException e) {
             }
         }
@@ -154,7 +153,7 @@
         try {
             if (appInfo.icon != 0) {
                 try {
-                    icon = res.getDrawable(appInfo.icon);
+                    icon = appInfo.loadIcon(pm);
                 } catch (Resources.NotFoundException e) {
                 }
             }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
index f77318c..afb2ea4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
@@ -16,13 +16,14 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.SystemClock;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import java.io.File;
 import java.io.IOException;
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
index c3e9c23..86b0321 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
@@ -16,11 +16,12 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.annotation.NonNull;
+
 /**
  * Receives uninstall events and persists them using a {@link EventResultPersister}.
  */
@@ -58,7 +59,7 @@
      *
      * @return The id for this event
      */
-    static int addObserver(@NonNull Context context, int id,
+    public static int addObserver(@NonNull Context context, int id,
             @NonNull EventResultPersister.EventResultObserver observer)
             throws EventResultPersister.OutOfIdsException {
         return getReceiver(context).addObserver(id, observer);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
index 973ab89..b9552fc 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
@@ -16,29 +16,27 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.admin.IDevicePolicyManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.graphics.drawable.Icon;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+
 import java.util.List;
 
 /**
@@ -94,28 +92,24 @@
 
                 switch (legacyStatus) {
                     case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
-                        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
-                                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
                         // Find out if the package is an active admin for some non-current user.
-                        int myUserId = UserHandle.myUserId();
-                        UserInfo otherBlockingUser = null;
-                        for (UserInfo user : userManager.getUsers()) {
+                        UserHandle myUserHandle = Process.myUserHandle();
+                        UserHandle otherBlockingUserHandle = null;
+                        for (UserHandle otherUserHandle : userManager.getUserHandles(true)) {
                             // We only catch the case when the user in question is neither the
                             // current user nor its profile.
-                            if (isProfileOfOrSame(userManager, myUserId, user.id)) {
+                            if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
                                 continue;
                             }
-
-                            try {
-                                if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) {
-                                    otherBlockingUser = user;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                Log.e(LOG_TAG, "Failed to talk to package manager", e);
+                            DevicePolicyManager dpm =
+                                    context.createContextAsUser(otherUserHandle, 0)
+                                    .getSystemService(DevicePolicyManager.class);
+                            if (dpm.packageHasActiveAdmins(appInfo.packageName)) {
+                                otherBlockingUserHandle = otherUserHandle;
+                                break;
                             }
                         }
-                        if (otherBlockingUser == null) {
+                        if (otherBlockingUserHandle == null) {
                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
                                     + " is a device admin");
 
@@ -124,46 +118,41 @@
                                     R.string.uninstall_failed_device_policy_manager));
                         } else {
                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
-                                    + " is a device admin of user " + otherBlockingUser);
+                                    + " is a device admin of user " + otherBlockingUserHandle);
 
+                            String userName =
+                                    context.createContextAsUser(otherBlockingUserHandle, 0)
+                                            .getSystemService(UserManager.class).getUserName();
                             setBigText(uninstallFailedNotification, String.format(context.getString(
                                     R.string.uninstall_failed_device_policy_manager_of_user),
-                                    otherBlockingUser.name));
+                                    userName));
                         }
                         break;
                     }
                     case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
-                        IPackageManager packageManager = IPackageManager.Stub.asInterface(
-                                ServiceManager.getService("package"));
-
-                        List<UserInfo> users = userManager.getUsers();
-                        int blockingUserId = UserHandle.USER_NULL;
-                        for (int i = 0; i < users.size(); ++i) {
-                            final UserInfo user = users.get(i);
-                            try {
-                                if (packageManager.getBlockUninstallForUser(appInfo.packageName,
-                                        user.id)) {
-                                    blockingUserId = user.id;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                // Shouldn't happen.
-                                Log.e(LOG_TAG, "Failed to talk to package manager", e);
+                        PackageManager packageManager = context.getPackageManager();
+                        List<UserHandle> userHandles = userManager.getUserHandles(true);
+                        UserHandle otherBlockingUserHandle = null;
+                        for (int i = 0; i < userHandles.size(); ++i) {
+                            final UserHandle handle = userHandles.get(i);
+                            if (packageManager.canUserUninstall(appInfo.packageName, handle)) {
+                                otherBlockingUserHandle = handle;
+                                break;
                             }
                         }
 
-                        int myUserId = UserHandle.myUserId();
-                        if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+                        UserHandle myUserHandle = Process.myUserHandle();
+                        if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) {
                             addDeviceManagerButton(context, uninstallFailedNotification);
                         } else {
                             addManageUsersButton(context, uninstallFailedNotification);
                         }
 
-                        if (blockingUserId == UserHandle.USER_NULL) {
+                        if (otherBlockingUserHandle == null) {
                             Log.d(LOG_TAG,
                                     "Uninstall failed for " + appInfo.packageName + " with code "
                                             + returnCode + " no blocking user");
-                        } else if (blockingUserId == UserHandle.USER_SYSTEM) {
+                        } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
                             setBigText(uninstallFailedNotification,
                                     context.getString(R.string.uninstall_blocked_device_owner));
                         } else {
@@ -200,18 +189,18 @@
      * Is a profile part of a user?
      *
      * @param userManager The user manager
-     * @param userId The id of the user
-     * @param profileId The id of the profile
+     * @param userHandle The handle of the user
+     * @param profileHandle The handle of the profile
      *
      * @return If the profile is part of the user or the profile parent of the user
      */
-    private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) {
-        if (userId == profileId) {
+    private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
+            UserHandle profileHandle) {
+        if (userHandle.equals(profileHandle)) {
             return true;
         }
-
-        UserInfo parentUser = userManager.getProfileParent(profileId);
-        return parentUser != null && parentUser.id == userId;
+        return userManager.getProfileParent(profileHandle) != null
+                && userManager.getProfileParent(profileHandle).equals(userHandle);
     }
 
     /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 1485352..b60aba8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -16,9 +16,7 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -27,19 +25,18 @@
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 /**
  * Start an uninstallation, show a dialog while uninstalling and return result to the caller.
  */
@@ -57,7 +54,7 @@
 
     private int mUninstallId;
     private ApplicationInfo mAppInfo;
-    private IBinder mCallback;
+    private PackageManager.UninstallCompleteCallback mCallback;
     private boolean mReturnResult;
     private String mLabel;
 
@@ -68,7 +65,8 @@
         setFinishOnTouchOutside(false);
 
         mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
-        mCallback = getIntent().getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+        mCallback = getIntent().getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+                PackageManager.UninstallCompleteCallback.class);
         mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
         mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL);
 
@@ -119,15 +117,10 @@
                 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
 
-                try {
-                    ActivityThread.getPackageManager().getPackageInstaller().uninstall(
-                            new VersionedPackage(mAppInfo.packageName,
-                                    PackageManager.VERSION_CODE_HIGHEST),
-                            getPackageName(), flags, pendingIntent.getIntentSender(),
-                            user.getIdentifier());
-                } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
-                }
+                getPackageManager().getPackageInstaller().uninstall(
+                        new VersionedPackage(mAppInfo.packageName,
+                                PackageManager.VERSION_CODE_HIGHEST),
+                        flags, pendingIntent.getIntentSender());
             } else {
                 mUninstallId = savedInstanceState.getInt(UNINSTALL_ID);
                 UninstallEventReceiver.addObserver(this, mUninstallId, this);
@@ -135,7 +128,7 @@
         } catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) {
             Log.e(LOG_TAG, "Fails to start uninstall", e);
             onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
-                    null);
+                    null, 0);
         }
     }
 
@@ -152,15 +145,10 @@
     }
 
     @Override
-    public void onResult(int status, int legacyStatus, @Nullable String message) {
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
         if (mCallback != null) {
             // The caller will be informed about the result via a callback
-            final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
-                    .asInterface(mCallback);
-            try {
-                observer.onPackageDeleted(mAppInfo.packageName, legacyStatus, message);
-            } catch (RemoteException ignored) {
-            }
+            mCallback.onUninstallComplete(mAppInfo.packageName, legacyStatus, message);
         } else if (mReturnResult) {
             // The caller will be informed about the result and might decide to display it
             Intent result = new Intent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 0198168..04496b9 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -22,12 +22,7 @@
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.StringRes;
 import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.DialogFragment;
 import android.app.Fragment;
@@ -37,12 +32,9 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -50,13 +42,14 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
 import com.android.packageinstaller.handheld.ErrorDialogFragment;
 import com.android.packageinstaller.handheld.UninstallAlertDialogFragment;
 import com.android.packageinstaller.television.ErrorFragment;
@@ -80,7 +73,7 @@
         public ActivityInfo activityInfo;
         public boolean allUsers;
         public UserHandle user;
-        public IBinder callback;
+        public PackageManager.UninstallCompleteCallback callback;
     }
 
     private String mPackageName;
@@ -94,44 +87,8 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
-        try {
-            int callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken());
-
-            String callingPackage = getPackageNameForUid(callingUid);
-            if (callingPackage == null) {
-                Log.e(TAG, "Package not found for originating uid " + callingUid);
-                setResult(Activity.RESULT_FIRST_USER);
-                finish();
-                return;
-            } else {
-                AppOpsManager appOpsManager = (AppOpsManager) getSystemService(
-                        Context.APP_OPS_SERVICE);
-                if (appOpsManager.noteOpNoThrow(
-                        AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
-                        != MODE_ALLOWED) {
-                    Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
-                    setResult(Activity.RESULT_FIRST_USER);
-                    finish();
-                    return;
-                }
-            }
-
-            if (getMaxTargetSdkVersionForUid(this, callingUid)
-                    >= Build.VERSION_CODES.P && AppGlobals.getPackageManager().checkUidPermission(
-                    Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid)
-                    != PackageManager.PERMISSION_GRANTED
-                    && AppGlobals.getPackageManager().checkUidPermission(
-                            Manifest.permission.DELETE_PACKAGES, callingUid)
-                            != PackageManager.PERMISSION_GRANTED) {
-                Log.e(TAG, "Uid " + callingUid + " does not have "
-                        + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
-                        + Manifest.permission.DELETE_PACKAGES);
-
-                setResult(Activity.RESULT_FIRST_USER);
-                finish();
-                return;
-            }
-        } catch (RemoteException ex) {
+        int callingUid = getLaunchedFromUid();
+        if (callingUid == Process.INVALID_UID) {
             // Cannot reach Package/ActivityManager. Aborting uninstall.
             Log.e(TAG, "Could not determine the launching uid.");
 
@@ -140,6 +97,38 @@
             return;
         }
 
+        String callingPackage = getPackageNameForUid(callingUid);
+        if (callingPackage == null) {
+            Log.e(TAG, "Package not found for originating uid " + callingUid);
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        } else {
+            AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+            if (appOpsManager.noteOpNoThrow(
+                    AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
+                    != MODE_ALLOWED) {
+                Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
+                setResult(Activity.RESULT_FIRST_USER);
+                finish();
+                return;
+            }
+        }
+
+        if (getMaxTargetSdkVersionForUid(this, callingUid) >= Build.VERSION_CODES.P
+                && getBaseContext().checkPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
+                0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED
+                && getBaseContext().checkPermission(Manifest.permission.DELETE_PACKAGES,
+                0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED) {
+            Log.e(TAG, "Uid " + callingUid + " does not have "
+                    + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+                    + Manifest.permission.DELETE_PACKAGES);
+
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        }
+
         // Get intent information.
         // We expect an intent with URI of the form package://<packageName>#<className>
         // className is optional; if specified, it is the activity the user chose to uninstall
@@ -157,37 +146,37 @@
             return;
         }
 
-        final IPackageManager pm = IPackageManager.Stub.asInterface(
-                ServiceManager.getService("package"));
+        PackageManager pm = getPackageManager();
+        UserManager userManager = getBaseContext().getSystemService(UserManager.class);
 
         mDialogInfo = new DialogInfo();
 
         mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
-        if (mDialogInfo.allUsers && !UserManager.get(this).isAdminUser()) {
+        if (mDialogInfo.allUsers && !userManager.isAdminUser()) {
             Log.e(TAG, "Only admin user can request uninstall for all users");
             showUserIsNotAllowed();
             return;
         }
         mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER);
         if (mDialogInfo.user == null) {
-            mDialogInfo.user = android.os.Process.myUserHandle();
+            mDialogInfo.user = Process.myUserHandle();
         } else {
-            UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
             List<UserHandle> profiles = userManager.getUserProfiles();
             if (!profiles.contains(mDialogInfo.user)) {
-                Log.e(TAG, "User " + android.os.Process.myUserHandle() + " can't request uninstall "
+                Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
                         + "for user " + mDialogInfo.user);
                 showUserIsNotAllowed();
                 return;
             }
         }
 
-        mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+        mDialogInfo.callback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+                                            PackageManager.UninstallCompleteCallback.class);
 
         try {
             mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName,
-                    PackageManager.MATCH_ANY_USER, mDialogInfo.user.getIdentifier());
-        } catch (RemoteException e) {
+                    PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
+        } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Unable to get packageName. Package manager is dead?");
         }
 
@@ -202,9 +191,9 @@
         if (className != null) {
             try {
                 mDialogInfo.activityInfo = pm.getActivityInfo(
-                        new ComponentName(mPackageName, className), 0,
-                        mDialogInfo.user.getIdentifier());
-            } catch (RemoteException e) {
+                        new ComponentName(mPackageName, className),
+                        PackageManager.ComponentInfoFlags.of(0));
+            } catch (PackageManager.NameNotFoundException e) {
                 Log.e(TAG, "Unable to get className. Package manager is dead?");
                 // Continue as the ActivityInfo isn't critical.
             }
@@ -315,7 +304,6 @@
             newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
             newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
-
             if (returnResult) {
                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
             }
@@ -366,11 +354,10 @@
                 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
 
-                ActivityThread.getPackageManager().getPackageInstaller().uninstall(
+                getPackageManager().getPackageInstaller().uninstall(
                         new VersionedPackage(mDialogInfo.appInfo.packageName,
                                 PackageManager.VERSION_CODE_HIGHEST),
-                        getPackageName(), flags, pendingIntent.getIntentSender(),
-                        mDialogInfo.user.getIdentifier());
+                        flags, pendingIntent.getIntentSender());
             } catch (Exception e) {
                 notificationManager.cancel(uninstallId);
 
@@ -382,13 +369,8 @@
 
     public void dispatchAborted() {
         if (mDialogInfo != null && mDialogInfo.callback != null) {
-            final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface(
-                    mDialogInfo.callback);
-            try {
-                observer.onPackageDeleted(mPackageName,
-                        PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
-            } catch (RemoteException ignored) {
-            }
+            mDialogInfo.callback.onUninstallComplete(mPackageName,
+                    PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
         }
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index a1bc992..21f4be0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -16,10 +16,10 @@
 
 package com.android.packageinstaller.handheld;
 
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
 import static android.text.format.Formatter.formatFileSize;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -29,7 +29,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -41,6 +40,9 @@
 import android.widget.CheckBox;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.packageinstaller.R;
 import com.android.packageinstaller.UninstallerActivity;
 
@@ -91,11 +93,11 @@
         long appDataSize = 0;
 
         if (user == null) {
-            List<UserInfo> users = userManager.getUsers();
+            List<UserHandle> userHandles = userManager.getUserHandles(true);
 
-            int numUsers = users.size();
+            int numUsers = userHandles.size();
             for (int i = 0; i < numUsers; i++) {
-                appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id));
+                appDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
             }
         } else {
             appDataSize = getAppDataSizeForUser(pkg, user);
@@ -128,7 +130,7 @@
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
-        UserManager userManager = UserManager.get(getActivity());
+        UserManager userManager = getContext().getSystemService(UserManager.class);
         if (isUpdate) {
             if (isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_update_text));
@@ -139,20 +141,25 @@
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
             } else if (!dialogInfo.user.equals(myUserHandle)) {
-                UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                if (userInfo.isManagedProfile()
-                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                int userId = dialogInfo.user.getIdentifier();
+                UserManager customUserManager = getContext()
+                        .createContextAsUser(UserHandle.of(userId), 0)
+                        .getSystemService(UserManager.class);
+                String userName = customUserManager.getUserName();
+
+                if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
+                        && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_current_user_work_profile,
-                                    userInfo.name));
-                } else if (userInfo.isCloneProfile()
-                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                                    userName));
+                } else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
+                        && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     isClonedApp = true;
                     messageBuilder.append(getString(
                             R.string.uninstall_application_text_current_user_clone_profile));
                 } else {
                     messageBuilder.append(
-                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                            getString(R.string.uninstall_application_text_user, userName));
                 }
             } else if (isCloneProfile(myUserHandle)) {
                 isClonedApp = true;
@@ -238,8 +245,8 @@
         // Check if another instance of given package exists in clone user profile.
         if (cloneUser != null) {
             try {
-                if (getContext().getPackageManager()
-                        .getPackageUidAsUser(packageName, cloneUser.getIdentifier()) > 0) {
+                if (getContext().getPackageManager().getPackageUidAsUser(packageName,
+                        PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0) {
                     return true;
                 }
             } catch (PackageManager.NameNotFoundException e) {
@@ -273,7 +280,6 @@
      */
     private boolean isSingleUser(UserManager userManager) {
         final int userCount = userManager.getUserCount();
-        return userCount == 1
-                || (UserManager.isSplitSystemUser() && userCount == 2);
+        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 2d241ca..5c5720a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -16,10 +16,11 @@
 
 package com.android.packageinstaller.television;
 
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+
 import android.app.Activity;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -63,7 +64,7 @@
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
-        UserManager userManager = UserManager.get(getActivity());
+        UserManager userManager = getContext().getSystemService(UserManager.class);
         if (isUpdate) {
             if (isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_update_text));
@@ -74,15 +75,21 @@
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
             } else if (!dialogInfo.user.equals(myUserHandle)) {
-                UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                if (userInfo.isManagedProfile()
-                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                int userId = dialogInfo.user.getIdentifier();
+                UserManager customUserManager = getContext()
+                        .createContextAsUser(UserHandle.of(userId), 0)
+                        .getSystemService(UserManager.class);
+                String userName = customUserManager.getUserName();
+
+                if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
+                        && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
+
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_current_user_work_profile,
-                                    userInfo.name));
+                                    userName));
                 } else {
                     messageBuilder.append(
-                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                            getString(R.string.uninstall_application_text_user, userName));
                 }
             } else {
                 messageBuilder.append(getString(R.string.uninstall_application_text));
@@ -126,7 +133,6 @@
      */
     private boolean isSingleUser(UserManager userManager) {
         final int userCount = userManager.getUserCount();
-        return userCount == 1
-                || (UserManager.isSplitSystemUser() && userCount == 2);
+        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
index a4f217c..0c59d44 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
@@ -17,24 +17,20 @@
 package com.android.packageinstaller.television;
 
 import android.app.Activity;
-import android.app.admin.IDevicePolicyManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -42,8 +38,12 @@
 import android.view.KeyEvent;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.EventResultPersister;
 import com.android.packageinstaller.PackageUtil;
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.UninstallEventReceiver;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
@@ -55,14 +55,17 @@
  * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects
  * the application object of the application to uninstall.
  */
-public class UninstallAppProgress extends Activity {
+public class UninstallAppProgress extends Activity implements
+        EventResultPersister.EventResultObserver {
     private static final String TAG = "UninstallAppProgress";
 
     private static final String FRAGMENT_TAG = "progress_fragment";
+    private static final String BROADCAST_ACTION =
+            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
 
     private ApplicationInfo mAppInfo;
     private boolean mAllUsers;
-    private IBinder mCallback;
+    private PackageManager.UninstallCompleteCallback mCallback;
 
     private volatile int mResultCode = -1;
 
@@ -116,13 +119,7 @@
                 final String packageName = (String) msg.obj;
 
                 if (mCallback != null) {
-                    final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
-                            .asInterface(mCallback);
-                    try {
-                        observer.onPackageDeleted(mAppInfo.packageName, mResultCode,
-                                packageName);
-                    } catch (RemoteException ignored) {
-                    }
+                    mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, packageName);
                     finish();
                     return;
                 }
@@ -139,37 +136,34 @@
 
                 // Update the status text
                 final String statusText;
+                Context ctx = getBaseContext();
                 switch (msg.arg1) {
                     case PackageManager.DELETE_SUCCEEDED:
                         statusText = getString(R.string.uninstall_done);
                         // Show a Toast and finish the activity
-                        Context ctx = getBaseContext();
                         Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show();
                         setResultAndFinish();
                         return;
                     case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
                         UserManager userManager =
                                 (UserManager) getSystemService(Context.USER_SERVICE);
-                        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
-                                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
                         // Find out if the package is an active admin for some non-current user.
-                        int myUserId = UserHandle.myUserId();
-                        UserInfo otherBlockingUser = null;
-                        for (UserInfo user : userManager.getUsers()) {
+                        UserHandle myUserHandle =  Process.myUserHandle();
+                        UserHandle otherBlockingUserHandle = null;
+                        for (UserHandle otherUserHandle : userManager.getUserHandles(true)) {
                             // We only catch the case when the user in question is neither the
                             // current user nor its profile.
-                            if (isProfileOfOrSame(userManager, myUserId, user.id)) continue;
-
-                            try {
-                                if (dpm.packageHasActiveAdmins(packageName, user.id)) {
-                                    otherBlockingUser = user;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "Failed to talk to package manager", e);
+                            if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+                                continue;
+                            }
+                            DevicePolicyManager dpm = ctx.createContextAsUser(otherUserHandle, 0)
+                                    .getSystemService(DevicePolicyManager.class);
+                            if (dpm.packageHasActiveAdmins(packageName)) {
+                                otherBlockingUserHandle = otherUserHandle;
+                                break;
                             }
                         }
-                        if (otherBlockingUser == null) {
+                        if (otherBlockingUserHandle == null) {
                             Log.d(TAG, "Uninstall failed because " + packageName
                                     + " is a device admin");
                             getProgressFragment().setDeviceManagerButtonVisible(true);
@@ -177,45 +171,40 @@
                                     R.string.uninstall_failed_device_policy_manager);
                         } else {
                             Log.d(TAG, "Uninstall failed because " + packageName
-                                    + " is a device admin of user " + otherBlockingUser);
+                                    + " is a device admin of user " + otherBlockingUserHandle);
                             getProgressFragment().setDeviceManagerButtonVisible(false);
+                            String userName = ctx.createContextAsUser(otherBlockingUserHandle, 0)
+                                    .getSystemService(UserManager.class).getUserName();
                             statusText = String.format(
                                     getString(R.string.uninstall_failed_device_policy_manager_of_user),
-                                    otherBlockingUser.name);
+                                    userName);
                         }
                         break;
                     }
                     case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
                         UserManager userManager =
                                 (UserManager) getSystemService(Context.USER_SERVICE);
-                        IPackageManager packageManager = IPackageManager.Stub.asInterface(
-                                ServiceManager.getService("package"));
-                        List<UserInfo> users = userManager.getUsers();
-                        int blockingUserId = UserHandle.USER_NULL;
-                        for (int i = 0; i < users.size(); ++i) {
-                            final UserInfo user = users.get(i);
-                            try {
-                                if (packageManager.getBlockUninstallForUser(packageName,
-                                        user.id)) {
-                                    blockingUserId = user.id;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                // Shouldn't happen.
-                                Log.e(TAG, "Failed to talk to package manager", e);
+                        PackageManager packageManager = ctx.getPackageManager();
+                        List<UserHandle> userHandles = userManager.getUserHandles(true);
+                        UserHandle otherBlockingUserHandle = null;
+                        for (int i = 0; i < userHandles.size(); ++i) {
+                            final UserHandle handle = userHandles.get(i);
+                            if (packageManager.canUserUninstall(packageName, handle)) {
+                                otherBlockingUserHandle = handle;
+                                break;
                             }
                         }
-                        int myUserId = UserHandle.myUserId();
-                        if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+                        UserHandle myUserHandle = Process.myUserHandle();
+                        if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) {
                             getProgressFragment().setDeviceManagerButtonVisible(true);
                         } else {
                             getProgressFragment().setDeviceManagerButtonVisible(false);
                             getProgressFragment().setUsersButtonVisible(true);
                         }
                         // TODO: b/25442806
-                        if (blockingUserId == UserHandle.USER_SYSTEM) {
+                        if (otherBlockingUserHandle == UserHandle.SYSTEM) {
                             statusText = getString(R.string.uninstall_blocked_device_owner);
-                        } else if (blockingUserId == UserHandle.USER_NULL) {
+                        } else if (otherBlockingUserHandle == null) {
                             Log.d(TAG, "Uninstall failed for " + packageName + " with code "
                                     + msg.arg1 + " no blocking user");
                             statusText = getString(R.string.uninstall_failed);
@@ -239,12 +228,13 @@
         }
     }
 
-    private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
-        if (userId == profileId) {
+    private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
+            UserHandle profileHandle) {
+        if (userHandle.equals(profileHandle)) {
             return true;
         }
-        UserInfo parentUser = userManager.getProfileParent(profileId);
-        return parentUser != null && parentUser.id == userId;
+        return userManager.getProfileParent(profileHandle) != null
+                && userManager.getProfileParent(profileHandle).equals(userHandle);
     }
 
     @Override
@@ -253,7 +243,8 @@
 
         Intent intent = getIntent();
         mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
-        mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+        mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+                PackageManager.UninstallCompleteCallback.class);
 
         // This currently does not support going through a onDestroy->onCreate cycle. Hence if that
         // happened, just fail the operation for mysterious reasons.
@@ -261,12 +252,7 @@
             mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
 
             if (mCallback != null) {
-                final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
-                        .asInterface(mCallback);
-                try {
-                    observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
-                } catch (RemoteException ignored) {
-                }
+                mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, null);
                 finish();
             } else {
                 setResultAndFinish();
@@ -278,10 +264,9 @@
         mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
         if (user == null) {
-            user = android.os.Process.myUserHandle();
+            user = Process.myUserHandle();
         }
 
-        PackageDeleteObserver observer = new PackageDeleteObserver();
 
         // Make window transparent until initView is called. In many cases we can avoid showing the
         // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
@@ -291,11 +276,29 @@
         getWindow().setNavigationBarColor(Color.TRANSPARENT);
 
         try {
-            getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
-                    mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, user.getIdentifier());
+            int uninstallId = UninstallEventReceiver.addObserver(this,
+                    EventResultPersister.GENERATE_NEW_ID, this);
+
+            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+            broadcastIntent.setPackage(getPackageName());
+
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+                    broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                            | PendingIntent.FLAG_MUTABLE);
+
+            createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall(
+                    new VersionedPackage(mAppInfo.packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    mAllUsers ? PackageManager.DELETE_ALL_USERS : 0,
+                    pendingIntent.getIntentSender());
         } catch (IllegalArgumentException e) {
             // Couldn't find the package, no need to call uninstall.
             Log.w(TAG, "Could not find package, not deleting " + mAppInfo.packageName, e);
+        } catch (EventResultPersister.OutOfIdsException e) {
+            Log.e(TAG, "Fails to start uninstall", e);
+            onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+                    null, 0);
         }
 
         mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
@@ -306,13 +309,12 @@
         return mAppInfo;
     }
 
-    private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
-        public void packageDeleted(String packageName, int returnCode) {
-            Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
-            msg.arg1 = returnCode;
-            msg.obj = packageName;
-            mHandler.sendMessage(msg);
-        }
+    @Override
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+        Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
+        msg.arg1 = legacyStatus;
+        msg.obj = mAppInfo.packageName;
+        mHandler.sendMessage(msg);
     }
 
     public void setResultAndFinish() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
index af6d9c5..c2d95b2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller.television;
 
-import android.annotation.Nullable;
 import android.app.Fragment;
 import android.content.Intent;
 import android.os.Bundle;
@@ -28,6 +27,8 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.packageinstaller.PackageUtil;
 import com.android.packageinstaller.R;
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
index 063d789..8dd691d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -265,7 +265,7 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(action);
         mContext.registerReceiver(broadcastReceiver, intentFilter,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
+                Context.RECEIVER_EXPORTED);
 
         // Create a matching PendingIntent and use it to generate the IntentSender
         Intent broadcastIntent = new Intent(action);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
index 06b1c16..959257f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -19,14 +19,16 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
@@ -43,13 +45,18 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.Nullable;
+
 import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.EventResultPersister;
 import com.android.packageinstaller.PackageUtil;
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.UninstallEventReceiver;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -80,16 +87,30 @@
  *  adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
  *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
  */
-public class WearPackageInstallerService extends Service {
+public class WearPackageInstallerService extends Service
+        implements EventResultPersister.EventResultObserver {
     private static final String TAG = "WearPkgInstallerService";
 
     private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+    private static final String BROADCAST_ACTION =
+            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
 
     private final int START_INSTALL = 1;
     private final int START_UNINSTALL = 2;
 
     private int mInstallNotificationId = 1;
     private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+    private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+    private class UninstallParams {
+        public String mPackageName;
+        public PowerManager.WakeLock mLock;
+
+        UninstallParams(String packageName, PowerManager.WakeLock lock) {
+            mPackageName = packageName;
+            mLock = lock;
+        }
+    }
 
     private final class ServiceHandler extends Handler {
         public ServiceHandler(Looper looper) {
@@ -211,7 +232,6 @@
         }
         final PackageManager pm = getPackageManager();
         File tempFile = null;
-        int installFlags = 0;
         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
         boolean messageSent = false;
         try {
@@ -220,17 +240,14 @@
                 existingPkgInfo = pm.getPackageInfo(packageName,
                         PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
                 if (existingPkgInfo != null) {
-                    installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Replacing package:" + packageName);
+                    }
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Ignore this exception. We could not find the package, will treat as a new
                 // installation.
             }
-            if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Replacing package:" + packageName);
-                }
-            }
             // TODO(28021618): This was left as a temp file due to the fact that this code is being
             //       deprecated and that we need the bare minimum to continue working moving forward
             //       If this code is used as reference, this permission logic might want to be
@@ -366,21 +383,60 @@
         final String packageName = WearPackageArgs.getPackageName(argsBundle);
 
         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+        UninstallParams params = new UninstallParams(packageName, lock);
+        mServiceIdToParams.put(startId, params);
+
         final PackageManager pm = getPackageManager();
         try {
             PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
             getLabelAndUpdateNotification(packageName,
                     getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
 
+            int uninstallId = UninstallEventReceiver.addObserver(this,
+                    EventResultPersister.GENERATE_NEW_ID, this);
+
+            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+            broadcastIntent.setPackage(getPackageName());
+
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+                    broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                            | PendingIntent.FLAG_MUTABLE);
+
             // Found package, send uninstall request.
-            pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId),
-                    PackageManager.DELETE_ALL_USERS);
+            pm.getPackageInstaller().uninstall(
+                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    PackageManager.DELETE_ALL_USERS,
+                    pendingIntent.getIntentSender());
 
             Log.i(TAG, "Sent delete request for " + packageName);
         } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
             // Couldn't find the package, no need to call uninstall.
             Log.w(TAG, "Could not find package, not deleting " + packageName, e);
             finishService(lock, startId);
+        } catch (EventResultPersister.OutOfIdsException e) {
+            Log.e(TAG, "Fails to start uninstall", e);
+            finishService(lock, startId);
+        }
+    }
+
+    @Override
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+        if (mServiceIdToParams.containsKey(serviceId)) {
+            UninstallParams params = mServiceIdToParams.get(serviceId);
+            try {
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+                } else {
+                    Log.e(TAG, "Package uninstall failed " + params.mPackageName
+                            + ", returnCode " + legacyStatus);
+                }
+            } finally {
+                finishService(params.mLock, serviceId);
+            }
         }
     }
 
@@ -537,29 +593,6 @@
         }
     }
 
-    private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
-        private PowerManager.WakeLock mWakeLock;
-        private int mStartId;
-
-        private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) {
-            mWakeLock = wakeLock;
-            mStartId = startId;
-        }
-
-        public void packageDeleted(String packageName, int returnCode) {
-            try {
-                if (returnCode >= 0) {
-                    Log.i(TAG, "Package " + packageName + " was uninstalled.");
-                } else {
-                    Log.e(TAG, "Package uninstall failed " + packageName + ", returnCode " +
-                            returnCode);
-                }
-            } finally {
-                finishService(mWakeLock, mStartId);
-            }
-        }
-    }
-
     private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
             final String title) {
         int notifId;
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index c08169e..e878804 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -41,6 +41,7 @@
     context: Context,
     private val app: ApplicationInfo,
     private val op: Int,
+    private val setModeByUid: Boolean = false,
 ) : IAppOpsController {
     private val appOpsManager = context.appOpsManager
 
@@ -49,7 +50,11 @@
 
     override fun setAllowed(allowed: Boolean) {
         val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
-        appOpsManager.setMode(op, app.uid, app.packageName, mode)
+        if (setModeByUid) {
+            appOpsManager.setUidMode(op, app.uid, mode)
+        } else {
+            appOpsManager.setMode(op, app.uid, app.packageName, mode)
+        }
         _mode.postValue(mode)
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index a357832..ee21b81 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -49,6 +49,13 @@
     abstract val appOp: Int
     abstract val permission: String
 
+    /**
+     * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
+     *
+     * Security related app-ops should be set with setUidMode() instead of setMode().
+     */
+    open val setModeByUid = false
+
     /** These not changeable packages will also be hidden from app list. */
     private val notChangeablePackages =
         setOf("android", "com.android.systemui", context.packageName)
@@ -61,7 +68,7 @@
                 AppOpPermissionRecord(
                     app = app,
                     hasRequestPermission = app.packageName in packageNames,
-                    appOpsController = AppOpsController(context = context, app = app, op = appOp),
+                    appOpsController = createAppOpsController(app),
                 )
             }
         }
@@ -69,7 +76,14 @@
     override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
         app = app,
         hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
-        appOpsController = AppOpsController(context = context, app = app, op = appOp),
+        appOpsController = createAppOpsController(app),
+    )
+
+    private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(
+        context = context,
+        app = app,
+        op = appOp,
+        setModeByUid = setModeByUid,
     )
 
     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
new file mode 100644
index 0000000..668bfdf
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_ERRORED
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsControllerTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var appOpsManager: AppOpsManager
+
+    @Before
+    fun setUp() {
+        whenever(context.appOpsManager).thenReturn(appOpsManager)
+    }
+
+    @Test
+    fun setAllowed_setToTrue() {
+        val controller = AppOpsController(context = context, app = APP, op = OP)
+
+        controller.setAllowed(true)
+
+        verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, MODE_ALLOWED)
+    }
+
+    @Test
+    fun setAllowed_setToFalse() {
+        val controller = AppOpsController(context = context, app = APP, op = OP)
+
+        controller.setAllowed(false)
+
+        verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, MODE_ERRORED)
+    }
+
+    @Test
+    fun setAllowed_setToTrueByUid() {
+        val controller =
+            AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+
+        controller.setAllowed(true)
+
+        verify(appOpsManager).setUidMode(OP, APP.uid, MODE_ALLOWED)
+    }
+
+    @Test
+    fun setAllowed_setToFalseByUid() {
+        val controller =
+            AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+
+        controller.setAllowed(false)
+
+        verify(appOpsManager).setUidMode(OP, APP.uid, MODE_ERRORED)
+    }
+
+    @Test
+    fun getMode() {
+        whenever(
+            appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName)
+        ).thenReturn(MODE_ALLOWED)
+        val controller = AppOpsController(context = context, app = APP, op = OP)
+
+        val mode = controller.getMode()
+
+        assertThat(mode).isEqualTo(MODE_ALLOWED)
+    }
+
+    private companion object {
+        const val OP = 1
+        val APP = ApplicationInfo().apply {
+            packageName = "package.name"
+            uid = 123
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index cd9c048..966b869 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
@@ -37,6 +38,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 import org.mockito.Mockito.`when` as whenever
@@ -50,17 +53,20 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    @Spy
     private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Mock
     private lateinit var packageManagers: IPackageManagers
 
+    @Mock
+    private lateinit var appOpsManager: AppOpsManager
+
     private lateinit var listModel: TestAppOpPermissionAppListModel
 
     @Before
-    fun setUp() = runTest {
-        whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION))
-            .thenReturn(emptySet())
+    fun setUp() {
+        whenever(context.appOpsManager).thenReturn(appOpsManager)
         listModel = TestAppOpPermissionAppListModel()
     }
 
@@ -221,6 +227,16 @@
         assertThat(appOpsController.setAllowedCalledWith).isTrue()
     }
 
+    @Test
+    fun setAllowed_setModeByUid() {
+        listModel.setModeByUid = true
+        val record = listModel.transformItem(APP)
+
+        listModel.setAllowed(record = record, newAllowed = true)
+
+        verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED)
+    }
+
     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
         lateinit var isAllowedState: State<Boolean?>
         composeTestRule.setContent {
@@ -236,6 +252,7 @@
         override val footerResId = R.string.test_app_op_permission_footer
         override val appOp = AppOpsManager.OP_MANAGE_MEDIA
         override val permission = PERMISSION
+        override var setModeByUid = false
     }
 
     private companion object {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 3e710e4..28353ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.util.KeyValueListParser;
@@ -55,6 +56,10 @@
     public static final String EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL =
             "extra_power_save_mode_trigger_level";
 
+    /** Battery saver schedule keys. */
+    public static final String KEY_NO_SCHEDULE = "key_battery_saver_no_schedule";
+    public static final String KEY_PERCENTAGE = "key_battery_saver_percentage";
+
     private BatterySaverUtils() {
     }
 
@@ -108,7 +113,6 @@
      * - If it's 4th time through 8th time, show the schedule suggestion notification.
      *
      * @param enable true to enable battery saver.
-     *
      * @return true if the request succeeded.
      */
     public static synchronized boolean setPowerSaveMode(Context context,
@@ -154,10 +158,10 @@
      * Shows the battery saver confirmation warning if it hasn't been acknowledged by the user in
      * the past before. Various extras can be provided that will change the behavior of this
      * notification as well as the ui for it.
-     * @param context A valid context
-     * @param extras Any extras to include in the intent to trigger this confirmation that will
-     * help the system disambiguate what to show/do
      *
+     * @param context A valid context
+     * @param extras  Any extras to include in the intent to trigger this confirmation that will
+     *                help the system disambiguate what to show/do
      * @return True if it showed the notification because it has not been previously acknowledged.
      * @see #EXTRA_CONFIRM_TEXT_ONLY
      * @see #EXTRA_POWER_SAVE_MODE_TRIGGER
@@ -221,6 +225,7 @@
 
     /**
      * Reverts battery saver schedule mode to none if routine mode is selected.
+     *
      * @param context a valid context
      */
     public static void revertScheduleToNoneIfNeeded(Context context) {
@@ -233,4 +238,50 @@
                     PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
         }
     }
+
+    /**
+     * Gets battery saver schedule mode.
+     *
+     * @param context a valid context
+     * @return battery saver schedule key
+     */
+    public static String getBatterySaverScheduleKey(Context context) {
+        final ContentResolver resolver = context.getContentResolver();
+        final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        if (mode == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) {
+            final int threshold =
+                    Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+            return threshold <= 0 ? KEY_NO_SCHEDULE : KEY_PERCENTAGE;
+        }
+        revertScheduleToNoneIfNeeded(context);
+        return KEY_NO_SCHEDULE;
+    }
+
+    /**
+     * Sets battery saver schedule mode.
+     *
+     * @param context      a valid context
+     * @param scheduleKey  {@link #KEY_NO_SCHEDULE} and {@link #KEY_PERCENTAGE}
+     * @param triggerLevel for automatic battery saver trigger level
+     */
+    public static void setBatterySaverScheduleMode(Context context, String scheduleKey,
+            int triggerLevel) {
+        final ContentResolver resolver = context.getContentResolver();
+        switch (scheduleKey) {
+            case KEY_NO_SCHEDULE:
+                Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                        PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+                Settings.Global.putInt(resolver, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+                break;
+            case KEY_PERCENTAGE:
+                Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                        PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+                Settings.Global.putInt(resolver,
+                        Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, triggerLevel);
+                break;
+            default:
+                throw new IllegalStateException("Not a valid schedule key");
+        }
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index 2bb3c2a..a15fe9f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -186,4 +189,46 @@
         assertThat(Secure.getInt(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, -1))
                 .isEqualTo(1);
     }
+
+    @Test
+    public void testGetBatterySaverScheduleKey_returnExpectedKey() {
+        Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+        Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+
+        assertThat(BatterySaverUtils.getBatterySaverScheduleKey(mMockContext)).isEqualTo(
+                KEY_NO_SCHEDULE);
+
+        Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 20);
+        Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+
+        assertThat(BatterySaverUtils.getBatterySaverScheduleKey(mMockContext)).isEqualTo(
+                KEY_PERCENTAGE);
+
+        Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 20);
+        Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC);
+
+        assertThat(BatterySaverUtils.getBatterySaverScheduleKey(mMockContext)).isEqualTo(
+                KEY_NO_SCHEDULE);
+    }
+
+    @Test
+    public void testSetBatterySaverScheduleMode_setSchedule() {
+        BatterySaverUtils.setBatterySaverScheduleMode(mMockContext, KEY_NO_SCHEDULE, -1);
+
+        assertThat(Global.getInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE, -1))
+                .isEqualTo(PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1))
+                .isEqualTo(0);
+
+        BatterySaverUtils.setBatterySaverScheduleMode(mMockContext, KEY_PERCENTAGE, 20);
+
+        assertThat(Global.getInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE, -1))
+                .isEqualTo(PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1))
+                .isEqualTo(20);
+
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ed1a0f3..1356e1d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -96,6 +96,7 @@
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.provider.Settings.SetAllResult;
+import android.provider.UpdatableDeviceConfigServiceReadiness;
 import android.provider.settings.validators.SystemSettingsValidators;
 import android.provider.settings.validators.Validator;
 import android.text.TextUtils;
@@ -416,10 +417,16 @@
             startWatchingUserRestrictionChanges();
         });
         ServiceManager.addService("settings", new SettingsService(this));
-        ServiceManager.addService("device_config", new DeviceConfigService(this));
+        addDeviceConfigServiceIfNeeded();
         return true;
     }
 
+    private void addDeviceConfigServiceIfNeeded() {
+        if (!UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
+            ServiceManager.addService("device_config", new DeviceConfigService(this));
+        }
+    }
+
     @Override
     public Bundle call(String method, String name, Bundle args) {
         final int requestingUserId = getRequestingUserId(args);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e1000e0..220c16a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -31,6 +31,52 @@
     ],
 }
 
+// Opt-in configuration for code depending on Jetpack Compose.
+soong_config_module_type {
+    name: "systemui_compose_java_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["SYSTEMUI_USE_COMPOSE"],
+    properties: [
+        "srcs",
+        "static_libs",
+    ],
+}
+
+systemui_compose_java_defaults {
+    name: "SystemUI_compose_defaults",
+    soong_config_variables: {
+        SYSTEMUI_USE_COMPOSE: {
+            // Because files in compose/features/ depend on SystemUI
+            // code, we compile those files when compiling SystemUI-core.
+            // We also compile the ComposeFacade in
+            // compose/facade/enabled/.
+            srcs: [
+                "compose/features/src/**/*.kt",
+                "compose/facade/enabled/src/**/*.kt",
+            ],
+
+            // The dependencies needed by SystemUIComposeFeatures,
+            // except for SystemUI-core.
+            // Copied from compose/features/Android.bp.
+            static_libs: [
+                "SystemUIComposeCore",
+
+                "androidx.compose.runtime_runtime",
+                "androidx.compose.material3_material3",
+                "androidx.activity_activity-compose",
+            ],
+
+            // By default, Compose is disabled and we compile the ComposeFacade
+            // in compose/facade/disabled/.
+            conditions_default: {
+                srcs: ["compose/facade/disabled/src/**/*.kt"],
+                static_libs: [],
+            },
+        },
+    },
+}
+
 java_library {
     name: "SystemUI-proto",
 
@@ -68,6 +114,9 @@
 
 android_library {
     name: "SystemUI-core",
+    defaults: [
+        "SystemUI_compose_defaults",
+    ],
     srcs: [
         "src/**/*.kt",
         "src/**/*.java",
@@ -228,6 +277,9 @@
 
 android_library {
     name: "SystemUI-tests",
+    defaults: [
+        "SystemUI_compose_defaults",
+    ],
     manifest: "tests/AndroidManifest-base.xml",
     additional_manifests: ["tests/AndroidManifest.xml"],
     srcs: [
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/runtime/MovableContent.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/runtime/MovableContent.kt
new file mode 100644
index 0000000..3f2f96b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/runtime/MovableContent.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.compose.runtime
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.InternalComposeApi
+import androidx.compose.runtime.MovableContent
+import androidx.compose.runtime.currentComposer
+
+/**
+ * An overload of [androidx.compose.runtime.movableContentOf] with 5 parameters.
+ *
+ * @see androidx.compose.runtime.movableContentOf
+ */
+@OptIn(InternalComposeApi::class)
+fun <P1, P2, P3, P4, P5> movableContentOf(
+    content: @Composable (P1, P2, P3, P4, P5) -> Unit
+): @Composable (P1, P2, P3, P4, P5) -> Unit {
+    val movableContent =
+        MovableContent<Pair<Triple<P1, P2, P3>, Pair<P4, P5>>> {
+            content(
+                it.first.first,
+                it.first.second,
+                it.first.third,
+                it.second.first,
+                it.second.second,
+            )
+        }
+    return { p1, p2, p3, p4, p5 ->
+        currentComposer.insertMovableContent(movableContent, Triple(p1, p2, p3) to (p4 to p5))
+    }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
new file mode 100644
index 0000000..6e728ce
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.compose
+
+import androidx.activity.ComponentActivity
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+
+/** The Compose facade, when Compose is *not* available. */
+object ComposeFacade : BaseComposeFacade {
+    override fun isComposeAvailable(): Boolean = false
+
+    override fun setPeopleSpaceActivityContent(
+        activity: ComponentActivity,
+        viewModel: PeopleViewModel,
+        onResult: (PeopleViewModel.Result) -> Unit,
+    ) {
+        throwComposeUnavailableError()
+    }
+
+    private fun throwComposeUnavailableError() {
+        error(
+            "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
+                " other function on ComposeFacade."
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
new file mode 100644
index 0000000..16294d9
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.compose
+
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import com.android.systemui.compose.theme.SystemUITheme
+import com.android.systemui.people.ui.compose.PeopleScreen
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+
+/** The Compose facade, when Compose is available. */
+object ComposeFacade : BaseComposeFacade {
+    override fun isComposeAvailable(): Boolean = true
+
+    override fun setPeopleSpaceActivityContent(
+        activity: ComponentActivity,
+        viewModel: PeopleViewModel,
+        onResult: (PeopleViewModel.Result) -> Unit,
+    ) {
+        activity.setContent { SystemUITheme { PeopleScreen(viewModel, onResult) } }
+    }
+}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 325ede6..4533330 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -35,6 +35,7 @@
 
         "androidx.compose.runtime_runtime",
         "androidx.compose.material3_material3",
+        "androidx.activity_activity-compose",
     ],
 
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 2aac46e..4a56b02 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -139,11 +139,20 @@
                     bottom = PeopleSpacePadding,
                     start = 8.dp,
                     end = 8.dp,
-                )
+                ),
         ) {
-            ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked)
-            item { Spacer(Modifier.height(35.dp)) }
-            ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
+            val hasPriorityConversations = priorityTiles.isNotEmpty()
+            if (hasPriorityConversations) {
+                ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked)
+            }
+
+            if (recentTiles.isNotEmpty()) {
+                if (hasPriorityConversations) {
+                    item { Spacer(Modifier.height(35.dp)) }
+                }
+
+                ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
+            }
         }
     }
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ce3084c..2eb58b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2870,7 +2870,7 @@
     Error message shown when a button should be pressed and held to activate it, usually shown when
     the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32].
     -->
-    <string name="keyguard_affordance_press_too_short">Press and hold to activate</string>
+    <string name="keyguard_affordance_press_too_short">Touch &amp; hold to open</string>
 
     <!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
     <string name="rear_display_bottom_sheet_cancel">Cancel</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index f974e27..edd150c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.view.ViewGroup
 import com.android.systemui.R
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -36,27 +38,29 @@
 @Inject
 constructor(
     private val context: Context,
-    unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+    statusBarStateController: StatusBarStateController,
+    unfoldProgressProvider: NaturalRotationUnfoldProgressProvider,
 ) {
 
     /** Certain views only need to move if they are not currently centered */
     var statusViewCentered = false
 
-    private val filterSplitShadeOnly = { !statusViewCentered }
-    private val filterNever = { true }
+    private val filterKeyguardAndSplitShadeOnly: () -> Boolean = {
+        statusBarStateController.getState() == KEYGUARD && !statusViewCentered }
+    private val filterKeyguard: () -> Boolean = { statusBarStateController.getState() == KEYGUARD }
 
     private val translateAnimator by lazy {
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
                 setOf(
-                    ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
+                    ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard),
                     ViewIdToTranslate(
-                        R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
-                    ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+                        R.id.lockscreen_clock_view_large, START, filterKeyguardAndSplitShadeOnly),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterKeyguard),
                     ViewIdToTranslate(
-                        R.id.notification_stack_scroller, END, filterSplitShadeOnly),
-                    ViewIdToTranslate(R.id.start_button, START, filterNever),
-                    ViewIdToTranslate(R.id.end_button, END, filterNever)),
+                        R.id.notification_stack_scroller, END, filterKeyguardAndSplitShadeOnly),
+                    ViewIdToTranslate(R.id.start_button, START, filterKeyguard),
+                    ViewIdToTranslate(R.id.end_button, END, filterKeyguard)),
             progressProvider = unfoldProgressProvider)
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e3c58ce..271fc7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -875,7 +875,7 @@
         Assert.isMainThread();
         if (mWakeOnFingerprintAcquiredStart && acquireInfo == FINGERPRINT_ACQUIRED_START) {
             mPowerManager.wakeUp(
-                    SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                    SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
                     "com.android.systemui.keyguard:FINGERPRINT_ACQUIRED_START");
         }
         for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 815ac68..e42f051 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -120,6 +120,7 @@
     private final Interpolator mLinearOutSlowIn;
     private final LockPatternUtils mLockPatternUtils;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
     private final InteractionJankMonitor mInteractionJankMonitor;
 
     // TODO: these should be migrated out once ready
@@ -141,7 +142,6 @@
     private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     private final @Background DelayableExecutor mBackgroundExecutor;
-    private boolean mIsOrientationChanged = false;
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
@@ -235,6 +235,7 @@
                 @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
                 @Nullable List<FaceSensorPropertiesInternal> faceProps,
                 @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+                @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
                 @NonNull UserManager userManager,
                 @NonNull LockPatternUtils lockPatternUtils,
                 @NonNull InteractionJankMonitor jankMonitor,
@@ -242,8 +243,9 @@
                 @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
             mConfig.mSensorIds = sensorIds;
             return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
-                    userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor,
-                    credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
+                    panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
+                    biometricPromptInteractor, credentialViewModelProvider,
+                    new Handler(Looper.getMainLooper()), bgExecutor);
         }
     }
 
@@ -331,6 +333,7 @@
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
@@ -346,6 +349,7 @@
         mHandler = mainHandler;
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPanelInteractionDetector = panelInteractionDetector;
 
         mTranslationY = getResources()
                 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
@@ -490,22 +494,6 @@
     @Override
     public void onOrientationChanged() {
         maybeUpdatePositionForUdfps(true /* invalidate */);
-        mIsOrientationChanged = true;
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        super.onWindowFocusChanged(hasWindowFocus);
-        if (!hasWindowFocus) {
-            //it's a workaround to avoid closing BP incorrectly
-            //BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true)
-            if (mIsOrientationChanged) {
-                mIsOrientationChanged = false;
-                return;
-            }
-            Log.v(TAG, "Lost window focus, dismissing the dialog");
-            animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
-        }
     }
 
     @Override
@@ -513,6 +501,8 @@
         super.onAttachedToWindow();
 
         mWakefulnessLifecycle.addObserver(this);
+        mPanelInteractionDetector.enable(
+                () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
 
         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
             mBiometricScrollView.addView(mBiometricView);
@@ -666,11 +656,6 @@
             mBiometricView.restoreState(savedState);
         }
 
-        if (savedState != null) {
-            mIsOrientationChanged = savedState.getBoolean(
-                    AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED);
-        }
-
         wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
     }
 
@@ -689,6 +674,7 @@
 
     @Override
     public void dismissWithoutCallback(boolean animate) {
+        mPanelInteractionDetector.disable();
         if (animate) {
             animateAway(false /* sendReason */, 0 /* reason */);
         } else {
@@ -699,6 +685,7 @@
 
     @Override
     public void dismissFromSystemServer() {
+        mPanelInteractionDetector.disable();
         animateAway(false /* sendReason */, 0 /* reason */);
     }
 
@@ -761,8 +748,6 @@
                 mBiometricView != null && mCredentialView == null);
         outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
 
-        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged);
-
         if (mBiometricView != null) {
             mBiometricView.onSaveState(outState);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a0f3ecb0..dad6ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -164,6 +164,7 @@
     @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
     @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
     private boolean mAllFingerprintAuthenticatorsRegistered;
     @NonNull private final UserManager mUserManager;
     @NonNull private final LockPatternUtils mLockPatternUtils;
@@ -721,6 +722,7 @@
             Provider<SideFpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull UdfpsLogger udfpsLogger,
@@ -767,6 +769,8 @@
                 });
 
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPanelInteractionDetector = panelInteractionDetector;
+
 
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
         int[] faceAuthLocation = context.getResources().getIntArray(
@@ -1149,6 +1153,7 @@
                 requestId,
                 multiSensorConfig,
                 mWakefulnessLifecycle,
+                mPanelInteractionDetector,
                 mUserManager,
                 mLockPatternUtils);
 
@@ -1239,6 +1244,7 @@
             String opPackageName, boolean skipIntro, long operationId, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils) {
         return new AuthContainerView.Builder(mContext)
@@ -1253,8 +1259,9 @@
                 .setMultiSensorConfig(multiSensorConfig)
                 .setScaleFactorProvider(() -> getScaleFactor())
                 .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
-                        userManager, lockPatternUtils, mInteractionJankMonitor,
-                        mBiometricPromptInteractor, mCredentialViewModelProvider);
+                        panelInteractionDetector, userManager, lockPatternUtils,
+                        mInteractionJankMonitor, mBiometricPromptInteractor,
+                        mCredentialViewModelProvider);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index cd0fc37..51f39b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -48,8 +48,6 @@
     String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
     String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
 
-    String KEY_BIOMETRIC_ORIENTATION_CHANGED = "orientation_changed";
-
     int SIZE_UNKNOWN = 0;
     /**
      * Minimal UI, showing only biometric icon.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
new file mode 100644
index 0000000..64211b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -0,0 +1,53 @@
+package com.android.systemui.biometrics
+
+import android.annotation.AnyThread
+import android.annotation.MainThread
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class AuthDialogPanelInteractionDetector
+@Inject
+constructor(
+    private val shadeExpansionStateManager: ShadeExpansionStateManager,
+    @Main private val mainExecutor: Executor,
+) {
+    private var action: Action? = null
+
+    @MainThread
+    fun enable(onPanelInteraction: Runnable) {
+        if (action == null) {
+            action = Action(onPanelInteraction)
+            shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+        } else {
+            Log.e(TAG, "Already enabled")
+        }
+    }
+
+    @MainThread
+    fun disable() {
+        if (action != null) {
+            action = null
+            shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged)
+        }
+    }
+
+    @AnyThread
+    private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
+        mainExecutor.execute {
+            action?.let {
+                if (event.tracking) {
+                    Log.v(TAG, "Detected panel interaction, event: $event")
+                    it.onPanelInteraction.run()
+                    disable()
+                }
+            }
+        }
+}
+
+private data class Action(val onPanelInteraction: Runnable)
+
+private const val TAG = "AuthDialogPanelInteractionDetector"
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 82e5704..805a20a 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -16,15 +16,21 @@
 
 package com.android.systemui.clipboardoverlay;
 
+import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
+
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+
+import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -56,6 +62,7 @@
     private final DeviceConfigProxy mDeviceConfig;
     private final Provider<ClipboardOverlayController> mOverlayProvider;
     private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
+    private final ClipboardToast mClipboardToast;
     private final ClipboardManager mClipboardManager;
     private final UiEventLogger mUiEventLogger;
     private final FeatureFlags mFeatureFlags;
@@ -66,6 +73,7 @@
     public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
             ClipboardOverlayControllerLegacyFactory overlayFactory,
+            ClipboardToast clipboardToast,
             ClipboardManager clipboardManager,
             UiEventLogger uiEventLogger,
             FeatureFlags featureFlags) {
@@ -73,6 +81,7 @@
         mDeviceConfig = deviceConfigProxy;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mOverlayFactory = overlayFactory;
+        mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager;
         mUiEventLogger = uiEventLogger;
         mFeatureFlags = featureFlags;
@@ -102,6 +111,15 @@
             return;
         }
 
+        if (!isUserSetupComplete()) {
+            // just show a toast, user should not access intents from this state
+            if (shouldShowToast(clipData)) {
+                mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
+                mClipboardToast.showCopiedToast();
+            }
+            return;
+        }
+
         boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
         if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
             mUsingNewOverlay = enabled;
@@ -136,10 +154,26 @@
         return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false);
     }
 
+    boolean shouldShowToast(ClipData clipData) {
+        if (clipData == null) {
+            return false;
+        } else if (clipData.getDescription().getClassificationStatus() == CLASSIFICATION_COMPLETE) {
+            // only show for classification complete if we aren't already showing a toast, to ignore
+            // the duplicate ClipData with classification
+            return !mClipboardToast.isShowing();
+        }
+        return true;
+    }
+
     private static boolean isEmulator() {
         return SystemProperties.getBoolean("ro.boot.qemu", false);
     }
 
+    private boolean isUserSetupComplete() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    }
+
     interface ClipboardOverlay {
         void setClipData(ClipData clipData, String clipSource);
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
index 9917507..4b5f876 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
@@ -43,7 +43,9 @@
     @UiEvent(doc = "clipboard overlay tapped outside")
     CLIPBOARD_OVERLAY_TAP_OUTSIDE(1077),
     @UiEvent(doc = "clipboard overlay dismissed, miscellaneous reason")
-    CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078);
+    CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078),
+    @UiEvent(doc = "clipboard toast shown")
+    CLIPBOARD_TOAST_SHOWN(1270);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
new file mode 100644
index 0000000..0ed7d27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.clipboardoverlay;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * Utility class for showing a simple clipboard toast on copy.
+ */
+class ClipboardToast extends Toast.Callback {
+    private final Context mContext;
+    private Toast mCopiedToast;
+
+    @Inject
+    ClipboardToast(Context context) {
+        mContext = context;
+    }
+
+    void showCopiedToast() {
+        if (mCopiedToast != null) {
+            mCopiedToast.cancel();
+        }
+        mCopiedToast = Toast.makeText(mContext,
+                R.string.clipboard_overlay_text_copied, Toast.LENGTH_SHORT);
+        mCopiedToast.show();
+    }
+
+    boolean isShowing() {
+        return mCopiedToast != null;
+    }
+
+    @Override // Toast.Callback
+    public void onToastHidden() {
+        super.onToastHidden();
+        mCopiedToast = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
new file mode 100644
index 0000000..e5ec727
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.compose
+
+import androidx.activity.ComponentActivity
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+
+/**
+ * A facade to interact with Compose, when it is available.
+ *
+ * You should access this facade by calling the static methods on
+ * [com.android.systemui.compose.ComposeFacade] directly.
+ */
+interface BaseComposeFacade {
+    /**
+     * Whether Compose is currently available. This function should be checked before calling any
+     * other functions on this facade.
+     *
+     * This value will never change at runtime.
+     */
+    fun isComposeAvailable(): Boolean
+
+    /** Bind the content of [activity] to [viewModel]. */
+    fun setPeopleSpaceActivityContent(
+        activity: ComponentActivity,
+        viewModel: PeopleViewModel,
+        onResult: (PeopleViewModel.Result) -> Unit,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 96707f4..59f68f7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.notification.fsi.FsiChromeViewModelFactory
+import com.android.systemui.statusbar.notification.fsi.FsiChromeViewBinder
 import com.android.systemui.statusbar.phone.KeyguardLiftController
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -94,6 +95,12 @@
     @ClassKey(FsiChromeViewModelFactory::class)
     abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable
 
+    /** Inject into FsiChromeWindowBinder.  */
+    @Binds
+    @IntoMap
+    @ClassKey(FsiChromeViewBinder::class)
+    abstract fun bindFsiChromeWindowBinder(sysui: FsiChromeViewBinder): CoreStartable
+
     /** Inject into GarbageMonitor.Service.  */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
index 16f4150..c746efd 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
@@ -20,7 +20,7 @@
 import android.database.ContentObserver
 import android.os.Handler
 import android.os.Looper
-import android.provider.Settings
+import com.android.systemui.util.settings.GlobalSettings
 
 /**
  * Class to track the availability of [DemoMode]. Use this class to track the availability and
@@ -29,7 +29,10 @@
  * This class works by wrapping a content observer for the relevant keys related to DemoMode
  * availability and current on/off state, and triggering callbacks.
  */
-abstract class DemoModeAvailabilityTracker(val context: Context) {
+abstract class DemoModeAvailabilityTracker(
+    val context: Context,
+    val globalSettings: GlobalSettings,
+) {
     var isInDemoMode = false
     var isDemoModeAvailable = false
 
@@ -41,9 +44,9 @@
     fun startTracking() {
         val resolver = context.contentResolver
         resolver.registerContentObserver(
-                Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
+                globalSettings.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
         resolver.registerContentObserver(
-                Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
+                globalSettings.getUriFor(DEMO_MODE_ON), false, onObserver)
     }
 
     fun stopTracking() {
@@ -57,12 +60,11 @@
     abstract fun onDemoModeFinished()
 
     private fun checkIsDemoModeAllowed(): Boolean {
-        return Settings.Global
-                .getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
+        return globalSettings.getInt(DEMO_MODE_ALLOWED, 0) != 0
     }
 
     private fun checkIsDemoModeOn(): Boolean {
-        return Settings.Global.getInt(context.contentResolver, DEMO_MODE_ON, 0) != 0
+        return globalSettings.getInt(DEMO_MODE_ON, 0) != 0
     }
 
     private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index 000bbe6..84f83f1 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -24,22 +24,28 @@
 import android.os.UserHandle
 import android.util.Log
 import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.demomode.DemoMode.ACTION_DEMO
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.util.Assert
 import com.android.systemui.util.settings.GlobalSettings
 import java.io.PrintWriter
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 
 /**
  * Handles system broadcasts for [DemoMode]
  *
  * Injected via [DemoModeModule]
  */
-class DemoModeController constructor(
+class DemoModeController
+constructor(
     private val context: Context,
     private val dumpManager: DumpManager,
-    private val globalSettings: GlobalSettings
+    private val globalSettings: GlobalSettings,
+    private val broadcastDispatcher: BroadcastDispatcher,
 ) : CallbackController<DemoMode>, Dumpable {
 
     // Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
@@ -58,9 +64,7 @@
         requestFinishDemoMode()
 
         val m = mutableMapOf<String, MutableList<DemoMode>>()
-        DemoMode.COMMANDS.map { command ->
-            m.put(command, mutableListOf())
-        }
+        DemoMode.COMMANDS.map { command -> m.put(command, mutableListOf()) }
         receiverMap = m
     }
 
@@ -71,7 +75,7 @@
 
         initialized = true
 
-        dumpManager.registerDumpable(TAG, this)
+        dumpManager.registerNormalDumpable(TAG, this)
 
         // Due to DemoModeFragment running in systemui:tuner process, we have to observe for
         // content changes to know if the setting turned on or off
@@ -81,8 +85,13 @@
 
         val demoFilter = IntentFilter()
         demoFilter.addAction(ACTION_DEMO)
-        context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter,
-                android.Manifest.permission.DUMP, null, Context.RECEIVER_EXPORTED)
+
+        broadcastDispatcher.registerReceiver(
+            receiver = broadcastReceiver,
+            filter = demoFilter,
+            user = UserHandle.ALL,
+            permission = android.Manifest.permission.DUMP,
+        )
     }
 
     override fun addCallback(listener: DemoMode) {
@@ -91,16 +100,15 @@
 
         commands.forEach { command ->
             if (!receiverMap.containsKey(command)) {
-                throw IllegalStateException("Command ($command) not recognized. " +
-                        "See DemoMode.java for valid commands")
+                throw IllegalStateException(
+                    "Command ($command) not recognized. " + "See DemoMode.java for valid commands"
+                )
             }
 
             receiverMap[command]!!.add(listener)
         }
 
-        synchronized(this) {
-            receivers.add(listener)
-        }
+        synchronized(this) { receivers.add(listener) }
 
         if (isInDemoMode) {
             listener.onDemoModeStarted()
@@ -109,14 +117,46 @@
 
     override fun removeCallback(listener: DemoMode) {
         synchronized(this) {
-            listener.demoCommands().forEach { command ->
-                receiverMap[command]!!.remove(listener)
-            }
+            listener.demoCommands().forEach { command -> receiverMap[command]!!.remove(listener) }
 
             receivers.remove(listener)
         }
     }
 
+    /**
+     * Create a [Flow] for the stream of demo mode arguments that come in for the given [command]
+     *
+     * This is equivalent of creating a listener manually and adding an event handler for the given
+     * command, like so:
+     *
+     * ```
+     * class Demoable {
+     *   private val demoHandler = object : DemoMode {
+     *     override fun demoCommands() = listOf(<command>)
+     *
+     *     override fun dispatchDemoCommand(command: String, args: Bundle) {
+     *       handleDemoCommand(args)
+     *     }
+     *   }
+     * }
+     * ```
+     *
+     * @param command The top-level demo mode command you want a stream for
+     */
+    fun demoFlowForCommand(command: String): Flow<Bundle> = conflatedCallbackFlow {
+        val callback =
+            object : DemoMode {
+                override fun demoCommands(): List<String> = listOf(command)
+
+                override fun dispatchDemoCommand(command: String, args: Bundle) {
+                    trySend(args)
+                }
+            }
+
+        addCallback(callback)
+        awaitClose { removeCallback(callback) }
+    }
+
     private fun setIsDemoModeAllowed(enabled: Boolean) {
         // Turn off demo mode if it was on
         if (isInDemoMode && !enabled) {
@@ -129,13 +169,9 @@
         Assert.isMainThread()
 
         val copy: List<DemoModeCommandReceiver>
-        synchronized(this) {
-            copy = receivers.toList()
-        }
+        synchronized(this) { copy = receivers.toList() }
 
-        copy.forEach { r ->
-            r.onDemoModeStarted()
-        }
+        copy.forEach { r -> r.onDemoModeStarted() }
     }
 
     private fun exitDemoMode() {
@@ -143,18 +179,13 @@
         Assert.isMainThread()
 
         val copy: List<DemoModeCommandReceiver>
-        synchronized(this) {
-            copy = receivers.toList()
-        }
+        synchronized(this) { copy = receivers.toList() }
 
-        copy.forEach { r ->
-            r.onDemoModeFinished()
-        }
+        copy.forEach { r -> r.onDemoModeFinished() }
     }
 
     fun dispatchDemoCommand(command: String, args: Bundle) {
         Assert.isMainThread()
-
         if (DEBUG) {
             Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
         }
@@ -172,9 +203,7 @@
         }
 
         // See? demo mode is easy now, you just notify the listeners when their command is called
-        receiverMap[command]!!.forEach { receiver ->
-            receiver.dispatchDemoCommand(command, args)
-        }
+        receiverMap[command]!!.forEach { receiver -> receiver.dispatchDemoCommand(command, args) }
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -183,65 +212,64 @@
         pw.println("  isDemoModeAllowed=$isAvailable")
         pw.print("  receivers=[")
         val copy: List<DemoModeCommandReceiver>
-        synchronized(this) {
-            copy = receivers.toList()
-        }
-        copy.forEach { recv ->
-            pw.print(" ${recv.javaClass.simpleName}")
-        }
+        synchronized(this) { copy = receivers.toList() }
+        copy.forEach { recv -> pw.print(" ${recv.javaClass.simpleName}") }
         pw.println(" ]")
         pw.println("  receiverMap= [")
         receiverMap.keys.forEach { command ->
             pw.print("    $command : [")
-            val recvs = receiverMap[command]!!.map { receiver ->
-                receiver.javaClass.simpleName
-            }.joinToString(",")
+            val recvs =
+                receiverMap[command]!!
+                    .map { receiver -> receiver.javaClass.simpleName }
+                    .joinToString(",")
             pw.println("$recvs ]")
         }
     }
 
-    private val tracker = object : DemoModeAvailabilityTracker(context) {
-        override fun onDemoModeAvailabilityChanged() {
-            setIsDemoModeAllowed(isDemoModeAvailable)
-        }
+    private val tracker =
+        object : DemoModeAvailabilityTracker(context, globalSettings) {
+            override fun onDemoModeAvailabilityChanged() {
+                setIsDemoModeAllowed(isDemoModeAvailable)
+            }
 
-        override fun onDemoModeStarted() {
-            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
-                enterDemoMode()
+            override fun onDemoModeStarted() {
+                if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                    enterDemoMode()
+                }
+            }
+
+            override fun onDemoModeFinished() {
+                if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                    exitDemoMode()
+                }
             }
         }
 
-        override fun onDemoModeFinished() {
-            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
-                exitDemoMode()
+    private val broadcastReceiver =
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                if (DEBUG) {
+                    Log.v(TAG, "onReceive: $intent")
+                }
+
+                val action = intent.action
+                if (!ACTION_DEMO.equals(action)) {
+                    return
+                }
+
+                val bundle = intent.extras ?: return
+                val command = bundle.getString("command", "").trim().lowercase()
+                if (command.isEmpty()) {
+                    return
+                }
+
+                try {
+                    dispatchDemoCommand(command, bundle)
+                } catch (t: Throwable) {
+                    Log.w(TAG, "Error running demo command, intent=$intent $t")
+                }
             }
         }
-    }
-
-    private val broadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (DEBUG) {
-                Log.v(TAG, "onReceive: $intent")
-            }
-
-            val action = intent.action
-            if (!ACTION_DEMO.equals(action)) {
-                return
-            }
-
-            val bundle = intent.extras ?: return
-            val command = bundle.getString("command", "").trim().toLowerCase()
-            if (command.length == 0) {
-                return
-            }
-
-            try {
-                dispatchDemoCommand(command, bundle)
-            } catch (t: Throwable) {
-                Log.w(TAG, "Error running demo command, intent=$intent $t")
-            }
-        }
-    }
 
     fun requestSetDemoModeAllowed(allowed: Boolean) {
         setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
@@ -258,10 +286,12 @@
     private fun setGlobal(key: String, value: Int) {
         globalSettings.putInt(key, value)
     }
+
+    companion object {
+        const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+        const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+    }
 }
 
 private const val TAG = "DemoModeController"
-private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
-private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
-
 private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
index de9affb..b84fa5a 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
@@ -37,8 +38,14 @@
     static DemoModeController provideDemoModeController(
             Context context,
             DumpManager dumpManager,
-            GlobalSettings globalSettings) {
-        DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
+            GlobalSettings globalSettings,
+            BroadcastDispatcher broadcastDispatcher
+    ) {
+        DemoModeController dmc = new DemoModeController(
+                context,
+                dumpManager,
+                globalSettings,
+                broadcastDispatcher);
         dmc.initialize();
         return /*run*/dmc;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 5d21349..5b90ef2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -16,7 +16,14 @@
 
 package com.android.systemui.doze;
 
+import static android.os.PowerManager.WAKE_REASON_BIOMETRIC;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_LIFT;
+import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_TAP;
+
 import android.annotation.IntDef;
+import android.os.PowerManager;
 import android.util.TimeUtils;
 
 import androidx.annotation.NonNull;
@@ -511,6 +518,25 @@
         }
     }
 
+    /**
+     * Converts {@link Reason} to {@link PowerManager.WakeReason}.
+     */
+    public static @PowerManager.WakeReason int getPowerManagerWakeReason(@Reason int wakeReason) {
+        switch (wakeReason) {
+            case REASON_SENSOR_DOUBLE_TAP:
+            case REASON_SENSOR_TAP:
+                return WAKE_REASON_TAP;
+            case REASON_SENSOR_PICKUP:
+                return WAKE_REASON_LIFT;
+            case REASON_SENSOR_UDFPS_LONG_PRESS:
+                return WAKE_REASON_BIOMETRIC;
+            case PULSE_REASON_DOCKING:
+                return WAKE_REASON_PLUGGED_IN;
+            default:
+                return WAKE_REASON_GESTURE;
+        }
+    }
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
             PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index f8bd1e7..ba38ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -116,8 +116,8 @@
 
     @Override
     public void requestWakeUp(@DozeLog.Reason int reason) {
-        PowerManager pm = getSystemService(PowerManager.class);
-        pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+        final PowerManager pm = getSystemService(PowerManager.class);
+        pm.wakeUp(SystemClock.uptimeMillis(), DozeLog.getPowerManagerWakeReason(reason),
                 "com.android.systemui:NODOZE " + DozeLog.reasonToString(reason));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
deleted file mode 100644
index ab4632b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2022 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.dreams
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.CallbackController
-import javax.inject.Inject
-
-/** Dream-related callback information */
-@SysUISingleton
-class DreamCallbackController @Inject constructor() :
-    CallbackController<DreamCallbackController.DreamCallback> {
-
-    private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()
-
-    override fun addCallback(callback: DreamCallbackController.DreamCallback) {
-        callbacks.add(callback)
-    }
-
-    override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
-        callbacks.remove(callback)
-    }
-
-    fun onWakeUp() {
-        callbacks.forEach { it.onWakeUp() }
-    }
-
-    interface DreamCallback {
-        fun onWakeUp()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index abe9355..c882f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -101,13 +101,11 @@
                             transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
                         }
                         .collect { px ->
-                            setElementsTranslationYAtPosition(
-                                px,
-                                ComplicationLayoutParams.POSITION_TOP
-                            )
-                            setElementsTranslationYAtPosition(
-                                px,
-                                ComplicationLayoutParams.POSITION_BOTTOM
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsTranslationYAtPosition(px, position)
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
                             )
                         }
                 }
@@ -115,15 +113,15 @@
                 /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
                 launch {
                     transitionViewModel.dreamOverlayAlpha.collect { alpha ->
-                        setElementsAlphaAtPosition(
-                            alpha = alpha,
-                            position = ComplicationLayoutParams.POSITION_TOP,
-                            fadingOut = true,
-                        )
-                        setElementsAlphaAtPosition(
-                            alpha = alpha,
-                            position = ComplicationLayoutParams.POSITION_BOTTOM,
-                            fadingOut = true,
+                        ComplicationLayoutParams.iteratePositions(
+                            { position: Int ->
+                                setElementsAlphaAtPosition(
+                                    alpha = alpha,
+                                    position = position,
+                                    fadingOut = true,
+                                )
+                            },
+                            POSITION_TOP or POSITION_BOTTOM
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
new file mode 100644
index 0000000..d5ff8f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.dreams
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Dream overlay-related callback information */
+@SysUISingleton
+class DreamOverlayCallbackController @Inject constructor() :
+    CallbackController<DreamOverlayCallbackController.Callback> {
+
+    private val callbacks = mutableSetOf<DreamOverlayCallbackController.Callback>()
+
+    var isDreaming = false
+        private set
+
+    override fun addCallback(callback: DreamOverlayCallbackController.Callback) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: DreamOverlayCallbackController.Callback) {
+        callbacks.remove(callback)
+    }
+
+    fun onWakeUp() {
+        isDreaming = false
+        callbacks.forEach { it.onWakeUp() }
+    }
+
+    fun onStartDream() {
+        isDreaming = true
+        callbacks.forEach { it.onStartDream() }
+    }
+
+    interface Callback {
+        /** Dream overlay has ended */
+        fun onWakeUp()
+
+        /** Dream overlay has started */
+        fun onStartDream()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1763dd9..fdc115b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -70,7 +70,7 @@
     // A controller for the dream overlay container view (which contains both the status bar and the
     // content area).
     private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
-    private final DreamCallbackController mDreamCallbackController;
+    private final DreamOverlayCallbackController mDreamOverlayCallbackController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Nullable
     private final ComponentName mLowLightDreamComponent;
@@ -151,7 +151,7 @@
             TouchInsetManager touchInsetManager,
             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
                     ComponentName lowLightDreamComponent,
-            DreamCallbackController dreamCallbackController) {
+            DreamOverlayCallbackController dreamOverlayCallbackController) {
         mContext = context;
         mExecutor = executor;
         mWindowManager = windowManager;
@@ -160,7 +160,7 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
-        mDreamCallbackController = dreamCallbackController;
+        mDreamOverlayCallbackController = dreamOverlayCallbackController;
 
         final ViewModelStore viewModelStore = new ViewModelStore();
         final Complication.Host host =
@@ -229,6 +229,7 @@
                     dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
             mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
 
+            mDreamOverlayCallbackController.onStartDream();
             mStarted = true;
         });
     }
@@ -245,7 +246,7 @@
     public void onWakeUp(@NonNull Runnable onCompletedCallback) {
         mExecutor.execute(() -> {
             if (mDreamOverlayContainerViewController != null) {
-                mDreamCallbackController.onWakeUp();
+                mDreamOverlayCallbackController.onWakeUp();
                 mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
             }
         });
@@ -255,6 +256,7 @@
      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
      * called from the main executing thread. The window attributes closely mirror those that are
      * set by the {@link android.service.dreams.DreamService} on the dream Window.
+     *
      * @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
      *                     into the dream window.
      */
@@ -301,7 +303,11 @@
 
     private void resetCurrentDreamOverlayLocked() {
         if (mStarted && mWindow != null) {
-            mWindowManager.removeView(mWindow.getDecorView());
+            try {
+                mWindowManager.removeView(mWindow.getDecorView());
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Error removing decor view when resetting overlay", e);
+            }
         }
 
         mStateController.setOverlayActive(false);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4268d3e..7b876d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -330,6 +330,8 @@
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
+    val SHOW_LOWLIGHT_ON_DIRECT_BOOT = unreleasedFlag(1003, "show_lowlight_on_direct_boot")
+
     // 1100 - windowing
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 9a0fbbf..a4fd087 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -29,8 +29,7 @@
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
-import com.android.systemui.dreams.DreamCallbackController
-import com.android.systemui.dreams.DreamCallbackController.DreamCallback
+import com.android.systemui.dreams.DreamOverlayCallbackController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -49,7 +48,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.merge
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
 interface KeyguardRepository {
@@ -81,6 +79,9 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Is an activity showing over the keyguard? */
+    val isKeyguardOccluded: Flow<Boolean>
+
     /** Observable for the signal that keyguard is about to go away. */
     val isKeyguardGoingAway: Flow<Boolean>
 
@@ -107,6 +108,9 @@
      */
     val isDreaming: Flow<Boolean>
 
+    /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
+    val isDreamingWithOverlay: Flow<Boolean>
+
     /**
      * Observable for the amount of doze we are currently in.
      *
@@ -179,7 +183,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
     private val authController: AuthController,
-    private val dreamCallbackController: DreamCallbackController,
+    private val dreamOverlayCallbackController: DreamOverlayCallbackController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -191,28 +195,55 @@
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition = _clockPosition.asStateFlow()
 
-    override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
-        val callback =
-            object : KeyguardStateController.Callback {
-                override fun onKeyguardShowingChanged() {
-                    trySendWithFailureLogging(
-                        keyguardStateController.isShowing,
-                        TAG,
-                        "updated isKeyguardShowing"
-                    )
-                }
+    override val isKeyguardShowing: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardStateController.Callback {
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isShowing,
+                                TAG,
+                                "updated isKeyguardShowing"
+                            )
+                        }
+                    }
+
+                keyguardStateController.addCallback(callback)
+                // Adding the callback does not send an initial update.
+                trySendWithFailureLogging(
+                    keyguardStateController.isShowing,
+                    TAG,
+                    "initial isKeyguardShowing"
+                )
+
+                awaitClose { keyguardStateController.removeCallback(callback) }
             }
+            .distinctUntilChanged()
 
-        keyguardStateController.addCallback(callback)
-        // Adding the callback does not send an initial update.
-        trySendWithFailureLogging(
-            keyguardStateController.isShowing,
-            TAG,
-            "initial isKeyguardShowing"
-        )
+    override val isKeyguardOccluded: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardStateController.Callback {
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isOccluded,
+                                TAG,
+                                "updated isKeyguardOccluded"
+                            )
+                        }
+                    }
 
-        awaitClose { keyguardStateController.removeCallback(callback) }
-    }
+                keyguardStateController.addCallback(callback)
+                // Adding the callback does not send an initial update.
+                trySendWithFailureLogging(
+                    keyguardStateController.isOccluded,
+                    TAG,
+                    "initial isKeyguardOccluded"
+                )
+
+                awaitClose { keyguardStateController.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
@@ -279,36 +310,45 @@
             }
             .distinctUntilChanged()
 
+    override val isDreamingWithOverlay: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DreamOverlayCallbackController.Callback {
+                        override fun onStartDream() {
+                            trySendWithFailureLogging(true, TAG, "updated isDreamingWithOverlay")
+                        }
+                        override fun onWakeUp() {
+                            trySendWithFailureLogging(false, TAG, "updated isDreamingWithOverlay")
+                        }
+                    }
+                dreamOverlayCallbackController.addCallback(callback)
+                trySendWithFailureLogging(
+                    dreamOverlayCallbackController.isDreaming,
+                    TAG,
+                    "initial isDreamingWithOverlay",
+                )
+
+                awaitClose { dreamOverlayCallbackController.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+
     override val isDreaming: Flow<Boolean> =
-        merge(
-                conflatedCallbackFlow {
-                    val callback =
-                        object : KeyguardUpdateMonitorCallback() {
-                            override fun onDreamingStateChanged(isDreaming: Boolean) {
-                                trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
-                            }
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onDreamingStateChanged(isDreaming: Boolean) {
+                            trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
                         }
-                    keyguardUpdateMonitor.registerCallback(callback)
-                    trySendWithFailureLogging(
-                        keyguardUpdateMonitor.isDreaming,
-                        TAG,
-                        "initial isDreaming",
-                    )
+                    }
+                keyguardUpdateMonitor.registerCallback(callback)
+                trySendWithFailureLogging(
+                    keyguardUpdateMonitor.isDreaming,
+                    TAG,
+                    "initial isDreaming",
+                )
 
-                    awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
-                },
-                conflatedCallbackFlow {
-                    val callback =
-                        object : DreamCallback {
-                            override fun onWakeUp() {
-                                trySendWithFailureLogging(false, TAG, "updated isDreaming")
-                            }
-                        }
-                    dreamCallbackController.addCallback(callback)
-
-                    awaitClose { dreamCallbackController.removeCallback(callback) }
-                }
-            )
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
             .distinctUntilChanged()
 
     override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index d72d7183b..343c2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -131,6 +131,10 @@
     }
 
     override fun startTransition(info: TransitionInfo): UUID? {
+        if (lastStep.from == info.from && lastStep.to == info.to) {
+            Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
+            return null
+        }
         if (lastStep.transitionState != TransitionState.FINISHED) {
             Log.i(TAG, "Transition still active: $lastStep, canceling")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
deleted file mode 100644
index dad166f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class AodToGoneTransitionInteractor
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
-
-    override fun start() {
-        scope.launch {
-            keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
-                .collect { pair ->
-                    val (biometricUnlockState, keyguardState) = pair
-                    if (
-                        keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
-                    }
-                }
-        }
-    }
-
-    private fun getAnimator(): ValueAnimator {
-        return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
-        }
-    }
-
-    companion object {
-        private const val TRANSITION_DURATION_MS = 500L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index f3d2905..c2d139c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -30,33 +31,33 @@
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class AodLockscreenTransitionInteractor
+class FromAodTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
-        listenForTransitionToAodFromLockscreen()
-        listenForTransitionToLockscreenFromDozeStates()
+        listenForAodToLockscreen()
+        listenForAodToGone()
     }
 
-    private fun listenForTransitionToAodFromLockscreen() {
+    private fun listenForAodToLockscreen() {
         scope.launch {
             keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+                .dozeTransitionTo(DozeStateModel.FINISH)
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (dozeToAod, lastStartedStep) = pair
-                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                    if (lastStartedStep.to == KeyguardState.AOD) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                KeyguardState.LOCKSCREEN,
                                 KeyguardState.AOD,
+                                KeyguardState.LOCKSCREEN,
                                 getAnimator(),
                             )
                         )
@@ -65,20 +66,20 @@
         }
     }
 
-    private fun listenForTransitionToLockscreenFromDozeStates() {
-        val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+    private fun listenForAodToGone() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.FINISH)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
-                    if (canGoToLockscreen.contains(lastStartedStep.to)) {
+                    val (biometricUnlockState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                lastStartedStep.to,
-                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.AOD,
+                                KeyguardState.GONE,
                                 getAnimator(),
                             )
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
index 056c44d..0e9c447 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
@@ -23,16 +23,18 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class BouncerToGoneTransitionInteractor
+class FromBouncerTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
@@ -40,15 +42,54 @@
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromBouncerTransitionInteractor::class.simpleName!!) {
 
     private var transitionId: UUID? = null
 
     override fun start() {
-        listenForKeyguardGoingAway()
+        listenForBouncerToGone()
+        listenForBouncerToLockscreenOrAod()
     }
 
-    private fun listenForKeyguardGoingAway() {
+    private fun listenForBouncerToLockscreenOrAod() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(
+                    combine(
+                        keyguardInteractor.wakefulnessModel,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+                    if (
+                        !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
+                    ) {
+                        val to =
+                            if (
+                                wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
+                                    wakefulnessState.state == WakefulnessState.ASLEEP
+                            ) {
+                                KeyguardState.AOD
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = to,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 95d9602..fd2d271 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,36 +21,46 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class LockscreenGoneTransitionInteractor
+class FromDozingTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
+        listenForDozingToLockscreen()
+    }
+
+    private fun listenForDozingToLockscreen() {
         scope.launch {
-            keyguardInteractor.isKeyguardGoingAway
+            keyguardInteractor.dozeTransitionModel
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
-                    val (isKeyguardGoingAway, lastStartedStep) = pair
-                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                    val (dozeTransitionModel, lastStartedTransition) = pair
+                    if (
+                        isDozeOff(dozeTransitionModel.to) &&
+                            lastStartedTransition.to == KeyguardState.DOZING
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
+                                KeyguardState.DOZING,
                                 KeyguardState.LOCKSCREEN,
-                                KeyguardState.GONE,
                                 getAnimator(),
                             )
                         )
@@ -59,14 +69,14 @@
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            setDuration(duration.inWholeMilliseconds)
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 10L
+        private val DEFAULT_DURATION = 500.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 188930c..3b09ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -36,75 +36,40 @@
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class DreamingTransitionInteractor
+class FromDreamingTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
-
-    private val canDreamFrom =
-        setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
+) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
-        listenForEntryToDreaming()
         listenForDreamingToLockscreen()
+        listenForDreamingToOccluded()
         listenForDreamingToGone()
         listenForDreamingToDozing()
     }
 
-    private fun listenForEntryToDreaming() {
-        scope.launch {
-            keyguardInteractor.isDreaming
-                .sample(
-                    combine(
-                        keyguardInteractor.dozeTransitionModel,
-                        keyguardTransitionInteractor.finishedKeyguardState,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { triple ->
-                    val (isDreaming, dozeTransitionModel, keyguardState) = triple
-                    // Dozing/AOD and dreaming have overlapping events. If the state remains in
-                    // FINISH, it means that doze mode is not running and DREAMING is ok to
-                    // commence.
-                    if (
-                        isDozeOff(dozeTransitionModel.to) &&
-                            isDreaming &&
-                            canDreamFrom.contains(keyguardState)
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                keyguardState,
-                                KeyguardState.DREAMING,
-                                getAnimator(),
-                            )
-                        )
-                    }
-                }
-        }
-    }
-
     private fun listenForDreamingToLockscreen() {
         scope.launch {
-            keyguardInteractor.isDreaming
+            // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
+            // otherwise would have gone through OCCLUDED first
+            keyguardInteractor.isDreamingWithOverlay
                 .sample(
                     combine(
                         keyguardInteractor.dozeTransitionModel,
                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair,
+                        ::Pair
                     ),
                     ::toTriple
                 )
                 .collect { triple ->
                     val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
                     if (
-                        isDozeOff(dozeTransitionModel.to) &&
-                            !isDreaming &&
+                        !isDreaming &&
+                            isDozeOff(dozeTransitionModel.to) &&
                             lastStartedTransition.to == KeyguardState.DREAMING
                     ) {
                         keyguardTransitionRepository.startTransition(
@@ -120,6 +85,42 @@
         }
     }
 
+    private fun listenForDreamingToOccluded() {
+        scope.launch {
+            keyguardInteractor.isDreaming
+                .sample(
+                    combine(
+                        keyguardInteractor.isKeyguardOccluded,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair,
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isDreaming, isOccluded, lastStartedTransition) = triple
+                    if (
+                        isOccluded &&
+                            !isDreaming &&
+                            (lastStartedTransition.to == KeyguardState.DREAMING ||
+                                lastStartedTransition.to == KeyguardState.LOCKSCREEN)
+                    ) {
+                        // At the moment, checking for LOCKSCREEN state above provides a corrective
+                        // action. There's no great signal to determine when the dream is ending
+                        // and a transition to OCCLUDED is beginning directly. For now, the solution
+                        // is DREAMING->LOCKSCREEN->OCCLUDED
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                lastStartedTransition.to,
+                                KeyguardState.OCCLUDED,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
     private fun listenForDreamingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index a50e759..553fafe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -30,19 +30,44 @@
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class GoneAodTransitionInteractor
+class FromGoneTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
+        listenForGoneToAod()
+        listenForGoneToDreaming()
+    }
+
+    private fun listenForGoneToDreaming() {
+        scope.launch {
+            keyguardInteractor.isAbleToDream
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .collect { pair ->
+                    val (isAbleToDream, keyguardState) = pair
+                    if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.GONE,
+                                KeyguardState.DREAMING,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForGoneToAod() {
         scope.launch {
             keyguardInteractor.wakefulnessModel
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (wakefulnessState, keyguardState) = pair
                     if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..326acc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class FromLockscreenTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val shadeRepository: ShadeRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
+
+    private var transitionId: UUID? = null
+
+    override fun start() {
+        listenForLockscreenToGone()
+        listenForLockscreenToOccluded()
+        listenForLockscreenToAod()
+        listenForLockscreenToBouncer()
+        listenForLockscreenToDreaming()
+        listenForLockscreenToBouncerDragging()
+    }
+
+    private fun listenForLockscreenToDreaming() {
+        scope.launch {
+            keyguardInteractor.isAbleToDream
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isAbleToDream, lastStartedTransition) = pair
+                    if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.DREAMING,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToBouncer() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isBouncerShowing, lastStartedTransitionStep) = pair
+                    if (
+                        isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.LOCKSCREEN,
+                                to = KeyguardState.BOUNCER,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+    private fun listenForLockscreenToBouncerDragging() {
+        scope.launch {
+            shadeRepository.shadeModel
+                .sample(
+                    combine(
+                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardInteractor.statusBarState,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (shadeModel, keyguardState, statusBarState) = triple
+
+                    val id = transitionId
+                    if (id != null) {
+                        // An existing `id` means a transition is started, and calls to
+                        // `updateTransition` will control it until FINISHED
+                        keyguardTransitionRepository.updateTransition(
+                            id,
+                            1f - shadeModel.expansionAmount,
+                            if (
+                                shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
+                            ) {
+                                transitionId = null
+                                TransitionState.FINISHED
+                            } else {
+                                TransitionState.RUNNING
+                            }
+                        )
+                    } else {
+                        // TODO (b/251849525): Remove statusbarstate check when that state is
+                        // integrated into KeyguardTransitionRepository
+                        if (
+                            keyguardState == KeyguardState.LOCKSCREEN &&
+                                shadeModel.isUserDragging &&
+                                statusBarState == KEYGUARD
+                        ) {
+                            transitionId =
+                                keyguardTransitionRepository.startTransition(
+                                    TransitionInfo(
+                                        ownerName = name,
+                                        from = KeyguardState.LOCKSCREEN,
+                                        to = KeyguardState.BOUNCER,
+                                        animator = null,
+                                    )
+                                )
+                        }
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToGone() {
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToOccluded() {
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded
+                .sample(
+                    combine(
+                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardInteractor.isDreaming,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isOccluded, keyguardState, isDreaming) = triple
+                    // Occlusion signals come from the framework, and should interrupt any
+                    // existing transition
+                    if (isOccluded && !isDreaming) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                keyguardState,
+                                KeyguardState.OCCLUDED,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToAod() {
+        scope.launch {
+            keyguardInteractor
+                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (dozeToAod, lastStartedStep) = pair
+                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.AOD,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index f3d2905..937e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -21,42 +21,43 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class AodLockscreenTransitionInteractor
+class FromOccludedTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
-        listenForTransitionToAodFromLockscreen()
-        listenForTransitionToLockscreenFromDozeStates()
+        listenForOccludedToLockscreen()
+        listenForOccludedToDreaming()
     }
 
-    private fun listenForTransitionToAodFromLockscreen() {
+    private fun listenForOccludedToDreaming() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+            keyguardInteractor.isAbleToDream
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
-                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                    val (isAbleToDream, keyguardState) = pair
+                    if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.AOD,
+                                KeyguardState.OCCLUDED,
+                                KeyguardState.DREAMING,
                                 getAnimator(),
                             )
                         )
@@ -65,19 +66,19 @@
         }
     }
 
-    private fun listenForTransitionToLockscreenFromDozeStates() {
-        val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+    private fun listenForOccludedToLockscreen() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.FINISH)
+            keyguardInteractor.isKeyguardOccluded
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
-                    if (canGoToLockscreen.contains(lastStartedStep.to)) {
+                    val (isOccluded, lastStartedKeyguardState) = pair
+                    // Occlusion signals come from the framework, and should interrupt any
+                    // existing transition
+                    if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                lastStartedStep.to,
+                                KeyguardState.OCCLUDED,
                                 KeyguardState.LOCKSCREEN,
                                 getAnimator(),
                             )
@@ -87,14 +88,15 @@
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            setDuration(duration.inWholeMilliseconds)
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 500L
+        private val DEFAULT_DURATION = 500.milliseconds
+        val TO_LOCKSCREEN_DURATION = 1183.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6912e1d..402c179 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -22,12 +22,16 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -52,8 +56,27 @@
      * but not vice-versa.
      */
     val isDreaming: Flow<Boolean> = repository.isDreaming
+    /** Whether the system is dreaming with an overlay active */
+    val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+
+    /**
+     * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
+     * that doze mode is not running and DREAMING is ok to commence.
+     */
+    val isAbleToDream: Flow<Boolean> =
+        merge(isDreaming, isDreamingWithOverlay)
+            .sample(
+                dozeTransitionModel,
+                { isDreaming, dozeTransitionModel ->
+                    isDreaming && isDozeOff(dozeTransitionModel.to)
+                }
+            )
+            .distinctUntilChanged()
+
     /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the keyguard is occluded (covered by an activity). */
+    val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
     /** Whether the keyguard is going away. */
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
     /** Whether the bouncer is showing or not. */
@@ -74,8 +97,8 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
-    fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
-        return dozeTransitionModel.filter { it.to == state }
+    fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
+        return dozeTransitionModel.filter { states.contains(it.to) }
     }
     fun isKeyguardShowing(): Boolean {
         return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index bb8b79a..fbed446 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -37,13 +37,13 @@
             // exhaustive
             val ret =
                 when (it) {
-                    is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromAodTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromDreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 6e25200..a59c407 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -89,6 +89,7 @@
                 KeyguardState.BOUNCER -> true
                 KeyguardState.LOCKSCREEN -> true
                 KeyguardState.GONE -> true
+                KeyguardState.OCCLUDED -> true
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
deleted file mode 100644
index 5cb7d70..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.WakefulnessState
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class LockscreenBouncerTransitionInteractor
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val keyguardInteractor: KeyguardInteractor,
-    private val shadeRepository: ShadeRepository,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
-
-    private var transitionId: UUID? = null
-
-    override fun start() {
-        listenForDraggingUpToBouncer()
-        listenForBouncer()
-    }
-
-    private fun listenForBouncer() {
-        scope.launch {
-            keyguardInteractor.isBouncerShowing
-                .sample(
-                    combine(
-                        keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { triple ->
-                    val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
-                    if (
-                        !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
-                    ) {
-                        val to =
-                            if (
-                                wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
-                                    wakefulnessState.state == WakefulnessState.ASLEEP
-                            ) {
-                                KeyguardState.AOD
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
-                        )
-                    } else if (
-                        isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
-                    }
-                    Unit
-                }
-        }
-    }
-
-    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
-    private fun listenForDraggingUpToBouncer() {
-        scope.launch {
-            shadeRepository.shadeModel
-                .sample(
-                    combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
-                        keyguardInteractor.statusBarState,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { triple ->
-                    val (shadeModel, keyguardState, statusBarState) = triple
-
-                    val id = transitionId
-                    if (id != null) {
-                        // An existing `id` means a transition is started, and calls to
-                        // `updateTransition` will control it until FINISHED
-                        keyguardTransitionRepository.updateTransition(
-                            id,
-                            1f - shadeModel.expansionAmount,
-                            if (
-                                shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
-                            ) {
-                                transitionId = null
-                                TransitionState.FINISHED
-                            } else {
-                                TransitionState.RUNNING
-                            }
-                        )
-                    } else {
-                        // TODO (b/251849525): Remove statusbarstate check when that state is
-                        // integrated into KeyguardTransitionRepository
-                        if (
-                            keyguardState == KeyguardState.LOCKSCREEN &&
-                                shadeModel.isUserDragging &&
-                                statusBarState == KEYGUARD
-                        ) {
-                            transitionId =
-                                keyguardTransitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.LOCKSCREEN,
-                                        to = KeyguardState.BOUNCER,
-                                        animator = null,
-                                    )
-                                )
-                        }
-                    }
-                }
-        }
-    }
-
-    private fun getAnimator(): ValueAnimator {
-        return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
-        }
-    }
-
-    companion object {
-        private const val TRANSITION_DURATION_MS = 300L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 5f63ae7..81fa233 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -32,25 +32,25 @@
 
     @Binds
     @IntoSet
-    abstract fun lockscreenBouncer(
-        impl: LockscreenBouncerTransitionInteractor
-    ): TransitionInteractor
+    abstract fun fromBouncer(impl: FromBouncerTransitionInteractor): TransitionInteractor
 
     @Binds
     @IntoSet
-    abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+    abstract fun fromLockscreen(impl: FromLockscreenTransitionInteractor): TransitionInteractor
 
-    @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+    @Binds @IntoSet abstract fun fromAod(impl: FromAodTransitionInteractor): TransitionInteractor
 
-    @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+    @Binds @IntoSet abstract fun fromGone(impl: FromGoneTransitionInteractor): TransitionInteractor
 
     @Binds
     @IntoSet
-    abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+    abstract fun fromDreaming(impl: FromDreamingTransitionInteractor): TransitionInteractor
 
     @Binds
     @IntoSet
-    abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+    abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor
 
-    @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
+    @Binds
+    @IntoSet
+    abstract fun fromDozing(impl: FromDozingTransitionInteractor): TransitionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 08ad3d5..4d24c14 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -31,4 +31,6 @@
     abstract fun start()
 
     fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
+
+    fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index dd908c4..c757986 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -57,4 +57,8 @@
      * with SWIPE security method or face unlock without bypass.
      */
     GONE,
+    /*
+     * An activity is displaying over the keyguard.
+     */
+    OCCLUDED,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index ae8edfe..b19795c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.SuppressLint
 import android.graphics.drawable.Animatable2
+import android.os.VibrationEffect
 import android.util.Size
 import android.util.TypedValue
 import android.view.MotionEvent
@@ -43,8 +44,11 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.pairwise
 import kotlin.math.pow
 import kotlin.math.sqrt
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -93,6 +97,7 @@
         view: ViewGroup,
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager?,
+        vibratorHelper: VibratorHelper?,
         messageDisplayer: (Int) -> Unit,
     ): Binding {
         val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
@@ -118,22 +123,48 @@
                             viewModel = buttonModel,
                             falsingManager = falsingManager,
                             messageDisplayer = messageDisplayer,
+                            vibratorHelper = vibratorHelper,
                         )
                     }
                 }
 
                 launch {
+                    viewModel.startButton
+                        .map { it.isActivated }
+                        .pairwise()
+                        .collect { (prev, next) ->
+                            when {
+                                !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
+                                prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
+                            }
+                        }
+                }
+
+                launch {
                     viewModel.endButton.collect { buttonModel ->
                         updateButton(
                             view = endButton,
                             viewModel = buttonModel,
                             falsingManager = falsingManager,
                             messageDisplayer = messageDisplayer,
+                            vibratorHelper = vibratorHelper,
                         )
                     }
                 }
 
                 launch {
+                    viewModel.endButton
+                        .map { it.isActivated }
+                        .pairwise()
+                        .collect { (prev, next) ->
+                            when {
+                                !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
+                                prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
+                            }
+                        }
+                }
+
+                launch {
                     viewModel.isOverlayContainerVisible.collect { isVisible ->
                         overlayContainer.visibility =
                             if (isVisible) {
@@ -239,6 +270,7 @@
         viewModel: KeyguardQuickAffordanceViewModel,
         falsingManager: FalsingManager?,
         messageDisplayer: (Int) -> Unit,
+        vibratorHelper: VibratorHelper?,
     ) {
         if (!viewModel.isVisible) {
             view.isVisible = false
@@ -312,7 +344,9 @@
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
             if (viewModel.useLongPress) {
-                view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer))
+                view.setOnTouchListener(
+                    OnTouchListener(view, viewModel, messageDisplayer, vibratorHelper)
+                )
             } else {
                 view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
             }
@@ -328,6 +362,7 @@
         private val view: View,
         private val viewModel: KeyguardQuickAffordanceViewModel,
         private val messageDisplayer: (Int) -> Unit,
+        private val vibratorHelper: VibratorHelper?,
     ) : View.OnTouchListener {
 
         private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
@@ -376,25 +411,38 @@
                     true
                 }
                 MotionEvent.ACTION_UP -> {
-                    if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
-                        messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
-                        val shakeAnimator =
-                            ObjectAnimator.ofFloat(
-                                view,
-                                "translationX",
-                                0f,
-                                view.context.resources
-                                    .getDimensionPixelSize(
-                                        R.dimen.keyguard_affordance_shake_amplitude
+                    cancel(
+                        onAnimationEnd =
+                            if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
+                                Runnable {
+                                    messageDisplayer.invoke(
+                                        R.string.keyguard_affordance_press_too_short
                                     )
-                                    .toFloat(),
-                                0f,
-                            )
-                        shakeAnimator.duration = 300
-                        shakeAnimator.interpolator = CycleInterpolator(5f)
-                        shakeAnimator.start()
-                    }
-                    cancel()
+                                    val amplitude =
+                                        view.context.resources
+                                            .getDimensionPixelSize(
+                                                R.dimen.keyguard_affordance_shake_amplitude
+                                            )
+                                            .toFloat()
+                                    val shakeAnimator =
+                                        ObjectAnimator.ofFloat(
+                                            view,
+                                            "translationX",
+                                            -amplitude / 2,
+                                            amplitude / 2,
+                                        )
+                                    shakeAnimator.duration =
+                                        ShakeAnimationDuration.inWholeMilliseconds
+                                    shakeAnimator.interpolator =
+                                        CycleInterpolator(ShakeAnimationCycles)
+                                    shakeAnimator.start()
+
+                                    vibratorHelper?.vibrate(Vibrations.Shake)
+                                }
+                            } else {
+                                null
+                            }
+                    )
                     true
                 }
                 MotionEvent.ACTION_CANCEL -> {
@@ -405,11 +453,11 @@
             }
         }
 
-        private fun cancel() {
+        private fun cancel(onAnimationEnd: Runnable? = null) {
             downTimestamp = 0L
             longPressAnimator?.cancel()
             longPressAnimator = null
-            view.animate().scaleX(1f).scaleY(1f)
+            view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
         }
 
         companion object {
@@ -461,4 +509,58 @@
         val indicationTextSizePx: Int,
         val buttonSizePx: Size,
     )
+
+    private val ShakeAnimationDuration = 300.milliseconds
+    private val ShakeAnimationCycles = 5f
+
+    object Vibrations {
+
+        private const val SmallVibrationScale = 0.3f
+        private const val BigVibrationScale = 0.6f
+
+        val Shake =
+            VibrationEffect.startComposition()
+                .apply {
+                    val vibrationDelayMs =
+                        (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
+                            .toInt()
+                    val vibrationCount = ShakeAnimationCycles.toInt() * 2
+                    repeat(vibrationCount) {
+                        addPrimitive(
+                            VibrationEffect.Composition.PRIMITIVE_TICK,
+                            SmallVibrationScale,
+                            vibrationDelayMs,
+                        )
+                    }
+                }
+                .compose()
+
+        val Activated =
+            VibrationEffect.startComposition()
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_TICK,
+                    BigVibrationScale,
+                    0,
+                )
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+                    0.1f,
+                    0,
+                )
+                .compose()
+
+        val Deactivated =
+            VibrationEffect.startComposition()
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_TICK,
+                    BigVibrationScale,
+                    0,
+                )
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
+                    0.1f,
+                    0,
+                )
+                .compose()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 402fac1..e164f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AnimationParams
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 7cc95a1..fba5f63 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -27,11 +27,15 @@
 import androidx.activity.ComponentActivity;
 import androidx.lifecycle.ViewModelProvider;
 
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.people.ui.view.PeopleViewBinder;
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
+
 /** People Tile Widget configuration activity that shows the user their conversation tiles. */
 public class PeopleSpaceActivity extends ComponentActivity {
 
@@ -58,13 +62,18 @@
         int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
         viewModel.onWidgetIdChanged(widgetId);
 
-        ViewGroup view = PeopleViewBinder.create(this);
-        PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this,
-                (result) -> {
-                    finishActivity(result);
-                    return null;
-                });
-        setContentView(view);
+        Function1<PeopleViewModel.Result, Unit> onResult = (result) -> {
+            finishActivity(result);
+            return null;
+        };
+
+        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+            ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
+        } else {
+            ViewGroup view = PeopleViewBinder.create(this);
+            PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
+            setContentView(view);
+        }
     }
 
     private void finishActivity(PeopleViewModel.Result result) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index a6447a5..5716a1d72 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -448,6 +448,10 @@
 
     // Any cleanup needed when the service is being destroyed.
     void onDestroy() {
+        if (mSaveInBgTask != null) {
+            // just log success/failure for the pre-existing screenshot
+            mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+        }
         removeWindow();
         releaseMediaPlayer();
         releaseContext();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index ba779c6..639172f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -19,6 +19,9 @@
 import android.content.Context
 import android.view.ViewGroup
 import com.android.systemui.R
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -30,17 +33,24 @@
 @SysUIUnfoldScope
 class NotificationPanelUnfoldAnimationController
 @Inject
-constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+constructor(
+    private val context: Context,
+    statusBarStateController: StatusBarStateController,
+    progressProvider: NaturalRotationUnfoldProgressProvider,
+) {
+
+    private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
+        statusBarStateController.getState() == SHADE_LOCKED }
 
     private val translateAnimator by lazy {
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
                 setOf(
-                    ViewIdToTranslate(R.id.quick_settings_panel, START),
-                    ViewIdToTranslate(R.id.notification_stack_scroller, END),
-                    ViewIdToTranslate(R.id.rightLayout, END),
-                    ViewIdToTranslate(R.id.clock, START),
-                    ViewIdToTranslate(R.id.date, START)),
+                    ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
+                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+                    ViewIdToTranslate(R.id.rightLayout, END, filterShade),
+                    ViewIdToTranslate(R.id.clock, START, filterShade),
+                    ViewIdToTranslate(R.id.date, START, filterShade)),
             progressProvider = progressProvider)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 31543df..3e9d89a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1374,8 +1374,8 @@
                 mFalsingManager,
                 mLockIconViewController,
                 stringResourceId ->
-                        mKeyguardIndicationController.showTransientIndication(stringResourceId)
-        );
+                        mKeyguardIndicationController.showTransientIndication(stringResourceId),
+                mVibratorHelper);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3a011c5..64b6e61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -19,6 +19,7 @@
 import android.app.StatusBarManager;
 import android.media.AudioManager;
 import android.media.session.MediaSessionLegacyHelper;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -242,7 +243,9 @@
                         () -> mService.wakeUpIfDozing(
                                 SystemClock.uptimeMillis(),
                                 mView,
-                                "LOCK_ICON_TOUCH"));
+                                "LOCK_ICON_TOUCH",
+                                PowerManager.WAKE_REASON_GESTURE)
+                );
 
                 // In case we start outside of the view bounds (below the status bar), we need to
                 // dispatch
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index bf622c9..db70065 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import android.hardware.display.AmbientDisplayConfiguration
+import android.os.PowerManager
 import android.os.SystemClock
 import android.os.UserHandle
 import android.provider.Settings
@@ -89,7 +90,8 @@
                 centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
                     notificationShadeWindowView,
-                    "PULSING_SINGLE_TAP"
+                    "PULSING_SINGLE_TAP",
+                    PowerManager.WAKE_REASON_TAP
                 )
             }
             return true
@@ -114,7 +116,9 @@
             centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
                     notificationShadeWindowView,
-                    "PULSING_DOUBLE_TAP")
+                    "PULSING_DOUBLE_TAP",
+                    PowerManager.WAKE_REASON_TAP
+            )
             return true
         }
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index b8302d7..905cc3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -5,6 +5,7 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
+import android.os.PowerManager
 import android.os.SystemClock
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
@@ -272,7 +273,12 @@
         // Bind the click listener of the shelf to go to the full shade
         notificationShelfController.setOnClickListener {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                centralSurfaces.wakeUpIfDozing(SystemClock.uptimeMillis(), it, "SHADE_CLICK")
+                centralSurfaces.wakeUpIfDozing(
+                        SystemClock.uptimeMillis(),
+                        it,
+                        "SHADE_CLICK",
+                        PowerManager.WAKE_REASON_GESTURE,
+                )
                 goToLockedShade(it)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 3516037..8f9365c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -64,6 +65,8 @@
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.ListenerSet;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -71,8 +74,6 @@
 import java.util.Optional;
 import java.util.function.Consumer;
 
-import dagger.Lazy;
-
 /**
  * Class for handling remote input state over a set of notifications. This class handles things
  * like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -120,7 +121,8 @@
                 View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) {
             mCentralSurfacesOptionalLazy.get().ifPresent(
                     centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                            SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK"));
+                            SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK",
+                            PowerManager.WAKE_REASON_GESTURE));
 
             final NotificationEntry entry = getNotificationForParent(view.getParent());
             mLogger.logInitialClick(entry, pendingIntent);
@@ -464,9 +466,6 @@
         riv.getController().setRemoteInputs(inputs);
         riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
         ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null;
-        if (parent != null) {
-            riv.setDefocusTargetHeight(parent.getHeight());
-        }
         riv.focusAnimated(parent);
         if (userMessageContent != null) {
             riv.setEditTextContent(userMessageContent);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index c630feb..976924a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -22,7 +22,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.os.PowerManager
-import android.os.PowerManager.WAKE_REASON_GESTURE
 import android.os.SystemClock
 import android.util.IndentingPrintWriter
 import android.view.MotionEvent
@@ -249,7 +248,7 @@
         }
         if (statusBarStateController.isDozing) {
             wakeUpCoordinator.willWakeUp = true
-            mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
+            mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:PULSEDRAG")
         }
         lockscreenShadeTransitionController.goToLockedShade(startingChild,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index b9074f0..6e74542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -1301,7 +1301,7 @@
             }
         }
         String wifi = args.getString("wifi");
-        if (wifi != null) {
+        if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
             boolean show = wifi.equals("show");
             String level = args.getString("level");
             if (level != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index c3ce593..705cf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification;
 
 import android.app.Notification;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -70,7 +71,8 @@
         }
 
         mCentralSurfacesOptional.ifPresent(centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
+                SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK",
+                PowerManager.WAKE_REASON_GESTURE));
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
         final NotificationEntry entry = row.getEntry();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt
new file mode 100644
index 0000000..1a3927b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt
@@ -0,0 +1,99 @@
+package com.android.systemui.statusbar.notification.fsi
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.WindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+@SysUISingleton
+class FsiChromeViewBinder
+@Inject
+constructor(
+    val context: Context,
+    val windowManager: WindowManager,
+    val viewModelFactory: FsiChromeViewModelFactory,
+    val layoutInflater: LayoutInflater,
+    val centralSurfaces: CentralSurfaces,
+    @Main val mainExecutor: Executor,
+    @Application val scope: CoroutineScope,
+) : CoreStartable {
+
+    companion object {
+        private const val classTag = "FsiChromeViewBinder"
+    }
+
+    private val fsiChromeView =
+        layoutInflater.inflate(R.layout.fsi_chrome_view, null /* root */, false /* attachToRoot */)
+            as FsiChromeView
+
+    var addedToWindowManager = false
+    var cornerRadius: Int = context.resources.getDimensionPixelSize(
+            R.dimen.notification_corner_radius)
+
+    override fun start() {
+        val methodTag = "start"
+        log("$classTag $methodTag ")
+
+        scope.launch {
+            log("$classTag $methodTag launch ")
+            viewModelFactory.viewModelFlow.collect { vm -> updateForViewModel(vm) }
+        }
+    }
+
+    private fun updateForViewModel(vm: FsiChromeViewModel?) {
+        val methodTag = "updateForViewModel"
+
+        if (vm == null) {
+            log("$classTag $methodTag viewModel is null, removing from window manager")
+
+            if (addedToWindowManager) {
+                windowManager.removeView(fsiChromeView)
+                addedToWindowManager = false
+            }
+            return
+        }
+
+        bindViewModel(vm, windowManager)
+
+        if (addedToWindowManager) {
+            log("$classTag $methodTag already addedToWindowManager")
+        } else {
+            windowManager.addView(fsiChromeView, FsiTaskViewConfig.getWmLayoutParams("PackageName"))
+            addedToWindowManager = true
+        }
+    }
+
+    private fun bindViewModel(
+        vm: FsiChromeViewModel,
+        windowManager: WindowManager,
+    ) {
+        log("$classTag bindViewModel")
+
+        fsiChromeView.appIconImageView.setImageDrawable(vm.appIcon)
+        fsiChromeView.appNameTextView.text = vm.appName
+
+        fsiChromeView.dismissButton.setOnClickListener { vm.onDismiss() }
+        fsiChromeView.fullscreenButton.setOnClickListener { vm.onFullscreen() }
+
+        vm.taskView.cornerRadius = cornerRadius.toFloat()
+        vm.taskView.startActivity(
+            vm.fsi,
+            FsiTaskViewConfig.getFillInIntent(),
+            FsiTaskViewConfig.getActivityOptions(context, windowManager),
+            FsiTaskViewConfig.getLaunchBounds(windowManager)
+        )
+
+        log("$classTag bindViewModel started taskview activity")
+        fsiChromeView.addView(vm.taskView)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt
new file mode 100644
index 0000000..034ab56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.statusbar.notification.fsi
+
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.os.Binder
+import android.view.ViewGroup
+import android.view.WindowManager
+
+/**
+ * Config for adding the FsiChromeView window to WindowManager and starting the FSI activity.
+ */
+class FsiTaskViewConfig {
+
+    companion object {
+
+        private const val classTag = "FsiTaskViewConfig"
+
+        fun getWmLayoutParams(packageName: String): WindowManager.LayoutParams {
+            val params: WindowManager.LayoutParams?
+            params =
+                WindowManager.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
+                        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
+                            WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER,
+                    PixelFormat.TRANSLUCENT
+                )
+            params.setTrustedOverlay()
+            params.fitInsetsTypes = 0
+            params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+            params.token = Binder()
+            params.packageName = packageName
+            params.layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+            params.privateFlags =
+                params.privateFlags or WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+            return params
+        }
+
+        fun getFillInIntent(): Intent {
+            val fillInIntent = Intent()
+            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+            // FLAG_ACTIVITY_NEW_TASK is auto-applied because
+            // we're starting the FSI activity from a non-Activity context
+            return fillInIntent
+        }
+
+        fun getLaunchBounds(windowManager: WindowManager): Rect {
+            // TODO(b/243421660) check this works for non-resizeable activity
+            return Rect()
+        }
+
+        fun getActivityOptions(context: Context, windowManager: WindowManager): ActivityOptions {
+            // Custom options so there is no activity transition animation
+            val options =
+                ActivityOptions.makeCustomAnimation(context, 0 /* enterResId */, 0 /* exitResId */)
+
+            options.taskAlwaysOnTop = true
+
+            options.pendingIntentLaunchFlags =
+                Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
+                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
+                    Intent.FLAG_ACTIVITY_NEW_TASK
+
+            options.launchBounds = getLaunchBounds(windowManager)
+            return options
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index d240d5a..9a8c5d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -420,7 +420,7 @@
         Runnable wakeUp = ()-> {
             if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
                 mLogger.i("bio wakelock: Authenticated, waking up...");
-                mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
                         "android.policy:BIOMETRIC");
             }
             Trace.beginSection("release wake-and-unlock");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index c7c6441..cf2f7742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.view.KeyEvent;
@@ -205,7 +206,10 @@
     @Override
     Lifecycle getLifecycle();
 
-    void wakeUpIfDozing(long time, View where, String why);
+    /**
+     * Wakes up the device if the device was dozing.
+     */
+    void wakeUpIfDozing(long time, View where, String why, @PowerManager.WakeReason int wakeReason);
 
     NotificationShadeWindowView getNotificationShadeWindowView();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c1ed10c..22ebcab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -887,8 +887,6 @@
         mKeyguardIndicationController.init();
 
         mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
-        mStatusBarStateController.addCallback(mStateListener,
-                SysuiStatusBarStateController.RANK_STATUS_BAR);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
@@ -1507,10 +1505,11 @@
      * @param why the reason for the wake up
      */
     @Override
-    public void wakeUpIfDozing(long time, View where, String why) {
+    public void wakeUpIfDozing(long time, View where, String why,
+            @PowerManager.WakeReason int wakeReason) {
         if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
             mPowerManager.wakeUp(
-                    time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
+                    time, wakeReason, "com.android.systemui:" + why);
             mWakeUpComingFromTouch = true;
             mFalsingCollector.onScreenOnFromTouch();
         }
@@ -1587,6 +1586,8 @@
 
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
+        mStatusBarStateController.addCallback(mStateListener,
+                SysuiStatusBarStateController.RANK_STATUS_BAR);
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
         mBiometricUnlockController.addBiometricModeListener(
                 new BiometricUnlockController.BiometricModeListener() {
@@ -3364,7 +3365,8 @@
         mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
         if (mBouncerShowing) {
-            wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE");
+            wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE",
+                    PowerManager.WAKE_REASON_GESTURE);
         }
         updateScrimController();
         if (!mBouncerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index c7be219..c72eb05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -42,6 +42,8 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -56,14 +58,17 @@
     private final int mIconSize;
 
     private StatusBarWifiView mWifiView;
+    private ModernStatusBarWifiView mModernWifiView;
     private boolean mDemoMode;
     private int mColor;
 
     private final MobileIconsViewModel mMobileIconsViewModel;
+    private final StatusBarLocation mLocation;
 
     public DemoStatusIcons(
             LinearLayout statusIcons,
             MobileIconsViewModel mobileIconsViewModel,
+            StatusBarLocation location,
             int iconSize
     ) {
         super(statusIcons.getContext());
@@ -71,6 +76,7 @@
         mIconSize = iconSize;
         mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
         mMobileIconsViewModel = mobileIconsViewModel;
+        mLocation = location;
 
         if (statusIcons instanceof StatusIconContainer) {
             setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -233,14 +239,14 @@
 
     public void addDemoWifiView(WifiIconState state) {
         Log.d(TAG, "addDemoWifiView: ");
-        // TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}.
         StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
 
         int viewIndex = getChildCount();
         // If we have mobile views, put wifi before them
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView) {
+            if (child instanceof StatusBarMobileView
+                    || child instanceof ModernStatusBarMobileView) {
                 viewIndex = i;
                 break;
             }
@@ -287,7 +293,7 @@
         ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
                 mobileContext,
                 "mobile",
-                mMobileIconsViewModel.viewModelForSub(subId)
+                mMobileIconsViewModel.viewModelForSub(subId, mLocation)
         );
 
         // mobile always goes at the end
@@ -296,6 +302,30 @@
     }
 
     /**
+     * Add a {@link ModernStatusBarWifiView}
+     */
+    public void addModernWifiView(LocationBasedWifiViewModel viewModel) {
+        Log.d(TAG, "addModernDemoWifiView: ");
+        ModernStatusBarWifiView view = ModernStatusBarWifiView
+                .constructAndBind(mContext, "wifi", viewModel);
+
+        int viewIndex = getChildCount();
+        // If we have mobile views, put wifi before them
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child instanceof StatusBarMobileView
+                    || child instanceof ModernStatusBarMobileView) {
+                viewIndex = i;
+                break;
+            }
+        }
+
+        mModernWifiView = view;
+        mModernWifiView.setStaticDrawableColor(mColor);
+        addView(view, viewIndex, createLayoutParams());
+    }
+
+    /**
      * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
      * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
      * update it, since the context (and thus the {@link Configuration}) may have changed
@@ -317,8 +347,14 @@
 
     public void onRemoveIcon(StatusIconDisplayable view) {
         if (view.getSlot().equals("wifi")) {
-            removeView(mWifiView);
-            mWifiView = null;
+            if (view instanceof StatusBarWifiView) {
+                removeView(mWifiView);
+                mWifiView = null;
+            } else if (view instanceof ModernStatusBarWifiView) {
+                Log.d(TAG, "onRemoveIcon: removing modern wifi view");
+                removeView(mModernWifiView);
+                mModernWifiView = null;
+            }
         } else if (view instanceof StatusBarMobileView) {
             StatusBarMobileView mobileView = matchingMobileView(view);
             if (mobileView != null) {
@@ -371,8 +407,14 @@
         if (mWifiView != null) {
             mWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
+        if (mModernWifiView != null) {
+            mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+        }
         for (StatusBarMobileView view : mMobileViews) {
             view.onDarkChanged(areas, darkIntensity, tint);
         }
+        for (ModernStatusBarMobileView view : mModernMobileViews) {
+            view.onDarkChanged(areas, darkIntensity, tint);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 2ce1163..e4227dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
 
 /**
  * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -65,12 +66,14 @@
         falsingManager: FalsingManager? = null,
         lockIconViewController: LockIconViewController? = null,
         messageDisplayer: MessageDisplayer? = null,
+        vibratorHelper: VibratorHelper? = null,
     ) {
         binding =
             bind(
                 this,
                 viewModel,
                 falsingManager,
+                vibratorHelper,
             ) {
                 messageDisplayer?.display(it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index df3ab49..1a14a036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -359,6 +359,7 @@
         // Whether or not these icons show up in dumpsys
         protected boolean mShouldLog = false;
         private StatusBarIconController mController;
+        private final StatusBarLocation mLocation;
 
         // Enables SystemUI demo mode to take effect in this group
         protected boolean mDemoable = true;
@@ -381,11 +382,12 @@
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+            mLocation = location;
 
             if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
-                mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+                mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
                 MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
             } else {
                 mMobileIconsViewModel = null;
@@ -394,7 +396,7 @@
             if (statusBarPipelineFlags.runNewWifiIconBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
-                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
             } else {
                 mWifiViewModel = null;
             }
@@ -495,6 +497,11 @@
 
             ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
             mGroup.addView(view, index, onCreateLayoutParams());
+
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+            }
+
             return view;
         }
 
@@ -569,7 +576,7 @@
                     .constructAndBind(
                             mobileContext,
                             slot,
-                            mMobileIconsViewModel.viewModelForSub(subId)
+                            mMobileIconsViewModel.viewModelForSub(subId, mLocation)
                         );
         }
 
@@ -686,6 +693,9 @@
             mIsInDemoMode = true;
             if (mDemoStatusIcons == null) {
                 mDemoStatusIcons = createDemoStatusIcons();
+                if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+                    mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+                }
             }
             mDemoStatusIcons.onDemoModeStarted();
         }
@@ -705,7 +715,12 @@
         }
 
         protected DemoStatusIcons createDemoStatusIcons() {
-            return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize);
+            return new DemoStatusIcons(
+                    (LinearLayout) mGroup,
+                    mMobileIconsViewModel,
+                    mLocation,
+                    mIconSize
+            );
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a1e0c50..da1c361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -20,6 +20,7 @@
 
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -270,7 +271,8 @@
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
         mCentralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK");
+                SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK",
+                PowerManager.WAKE_REASON_GESTURE);
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c350c78..0d01715 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import dagger.Binds
@@ -56,7 +56,7 @@
     @Binds
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
-    @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+    @Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
 
     @Binds
     abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 6c37f94..1aa954f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -26,6 +26,8 @@
 import android.telephony.TelephonyCallback.SignalStrengthsListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 
@@ -79,4 +81,72 @@
      * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
      */
     val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
-)
+) : Diffable<MobileConnectionModel> {
+    override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
+        if (prevVal.dataConnectionState != dataConnectionState) {
+            row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+        }
+
+        if (prevVal.isEmergencyOnly != isEmergencyOnly) {
+            row.logChange(COL_EMERGENCY, isEmergencyOnly)
+        }
+
+        if (prevVal.isRoaming != isRoaming) {
+            row.logChange(COL_ROAMING, isRoaming)
+        }
+
+        if (prevVal.operatorAlphaShort != operatorAlphaShort) {
+            row.logChange(COL_OPERATOR, operatorAlphaShort)
+        }
+
+        if (prevVal.isGsm != isGsm) {
+            row.logChange(COL_IS_GSM, isGsm)
+        }
+
+        if (prevVal.cdmaLevel != cdmaLevel) {
+            row.logChange(COL_CDMA_LEVEL, cdmaLevel)
+        }
+
+        if (prevVal.primaryLevel != primaryLevel) {
+            row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
+        }
+
+        if (prevVal.dataActivityDirection != dataActivityDirection) {
+            row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+        }
+
+        if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
+            row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
+        }
+
+        if (prevVal.resolvedNetworkType != resolvedNetworkType) {
+            row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+        row.logChange(COL_EMERGENCY, isEmergencyOnly)
+        row.logChange(COL_ROAMING, isRoaming)
+        row.logChange(COL_OPERATOR, operatorAlphaShort)
+        row.logChange(COL_IS_GSM, isGsm)
+        row.logChange(COL_CDMA_LEVEL, cdmaLevel)
+        row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
+        row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+        row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
+        row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
+    }
+
+    companion object {
+        const val COL_EMERGENCY = "EmergencyOnly"
+        const val COL_ROAMING = "Roaming"
+        const val COL_OPERATOR = "OperatorName"
+        const val COL_IS_GSM = "IsGsm"
+        const val COL_CDMA_LEVEL = "CdmaLevel"
+        const val COL_PRIMARY_LEVEL = "PrimaryLevel"
+        const val COL_CONNECTION_STATE = "ConnectionState"
+        const val COL_ACTIVITY_DIRECTION = "DataActivity"
+        const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
+        const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index a8cf35a..c50d82a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -21,22 +21,48 @@
 import android.telephony.TelephonyManager.EXTRA_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
 import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 
 /**
  * Encapsulates the data needed to show a network name for a mobile network. The data is parsed from
  * the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED].
  */
-sealed interface NetworkNameModel {
+sealed interface NetworkNameModel : Diffable<NetworkNameModel> {
     val name: String
 
     /** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */
-    data class Default(override val name: String) : NetworkNameModel
+    data class Default(override val name: String) : NetworkNameModel {
+        override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+            if (prevVal !is Default || prevVal.name != name) {
+                row.logChange(COL_NETWORK_NAME, "Default($name)")
+            }
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_NAME, "Default($name)")
+        }
+    }
 
     /**
      * This name has been derived from telephony intents. see
      * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
      */
-    data class Derived(override val name: String) : NetworkNameModel
+    data class Derived(override val name: String) : NetworkNameModel {
+        override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+            if (prevVal !is Derived || prevVal.name != name) {
+                row.logChange(COL_NETWORK_NAME, "Derived($name)")
+            }
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_NAME, "Derived($name)")
+        }
+    }
+
+    companion object {
+        const val COL_NETWORK_NAME = "networkName"
+    }
 }
 
 fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2fd415e6..40e9ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -20,6 +20,7 @@
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import kotlinx.coroutines.flow.Flow
@@ -39,6 +40,13 @@
 interface MobileConnectionRepository {
     /** The subscriptionId that this connection represents */
     val subId: Int
+
+    /**
+     * The table log buffer created for this connection. Will have the name "MobileConnectionLog
+     * [subId]"
+     */
+    val tableLogBuffer: TableLogBuffer
+
     /**
      * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
      * listener + model.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index d3ee85f..b252de8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -24,6 +24,8 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
@@ -60,6 +62,7 @@
     private val dataSource: DemoModeMobileConnectionDataSource,
     @Application private val scope: CoroutineScope,
     context: Context,
+    private val logFactory: TableLogBufferFactory,
 ) : MobileConnectionsRepository {
 
     private var demoCommandJob: Job? = null
@@ -149,7 +152,16 @@
 
     override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
         return connectionRepoCache[subId]
-            ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
+            ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
+    }
+
+    private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
+        val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100)
+
+        return DemoMobileConnectionRepository(
+            subId,
+            tableLogBuffer,
+        )
     }
 
     override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
@@ -260,7 +272,10 @@
     }
 }
 
-class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+class DemoMobileConnectionRepository(
+    override val subId: Int,
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepository {
     override val connectionInfo = MutableStateFlow(MobileConnectionModel())
 
     override val dataEnabled = MutableStateFlow(true)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index a1ae8ed..d4ddb85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -24,10 +24,8 @@
 import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -35,8 +33,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.shareIn
@@ -52,27 +48,7 @@
     demoModeController: DemoModeController,
     @Application scope: CoroutineScope,
 ) {
-    private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
-        val callback =
-            object : DemoMode {
-                override fun demoCommands(): List<String> = listOf(COMMAND_NETWORK)
-
-                override fun dispatchDemoCommand(command: String, args: Bundle) {
-                    trySend(args)
-                }
-
-                override fun onDemoModeFinished() {
-                    // Handled elsewhere
-                }
-
-                override fun onDemoModeStarted() {
-                    // Handled elsewhere
-                }
-            }
-
-        demoModeController.addCallback(callback)
-        awaitClose { demoModeController.removeCallback(callback) }
-    }
+    private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
 
     // If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
     // commands work and it's a little silly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 7e9a9ce..0b9e158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -36,6 +36,9 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -46,7 +49,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
@@ -59,6 +61,7 @@
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
@@ -79,6 +82,7 @@
     mobileMappingsProxy: MobileMappingsProxy,
     bgDispatcher: CoroutineDispatcher,
     logger: ConnectivityPipelineLogger,
+    mobileLogger: TableLogBuffer,
     scope: CoroutineScope,
 ) : MobileConnectionRepository {
     init {
@@ -92,10 +96,11 @@
 
     private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
 
+    override val tableLogBuffer: TableLogBuffer = mobileLogger
+
     override val connectionInfo: StateFlow<MobileConnectionModel> = run {
         var state = MobileConnectionModel()
         conflatedCallbackFlow {
-                // TODO (b/240569788): log all of these into the connectivity logger
                 val callback =
                     object :
                         TelephonyCallback(),
@@ -106,6 +111,7 @@
                         TelephonyCallback.CarrierNetworkListener,
                         TelephonyCallback.DisplayInfoListener {
                         override fun onServiceStateChanged(serviceState: ServiceState) {
+                            logger.logOnServiceStateChanged(serviceState, subId)
                             state =
                                 state.copy(
                                     isEmergencyOnly = serviceState.isEmergencyOnly,
@@ -116,6 +122,7 @@
                         }
 
                         override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                            logger.logOnSignalStrengthsChanged(signalStrength, subId)
                             val cdmaLevel =
                                 signalStrength
                                     .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
@@ -142,12 +149,14 @@
                             dataState: Int,
                             networkType: Int
                         ) {
+                            logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
                             state =
                                 state.copy(dataConnectionState = dataState.toDataConnectionType())
                             trySend(state)
                         }
 
                         override fun onDataActivity(direction: Int) {
+                            logger.logOnDataActivity(direction, subId)
                             state =
                                 state.copy(
                                     dataActivityDirection = direction.toMobileDataActivityModel()
@@ -156,6 +165,7 @@
                         }
 
                         override fun onCarrierNetworkChange(active: Boolean) {
+                            logger.logOnCarrierNetworkChange(active, subId)
                             state = state.copy(carrierNetworkChangeActive = active)
                             trySend(state)
                         }
@@ -163,6 +173,7 @@
                         override fun onDisplayInfoChanged(
                             telephonyDisplayInfo: TelephonyDisplayInfo
                         ) {
+                            logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
 
                             val networkType =
                                 if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
@@ -193,7 +204,11 @@
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
             .onEach { telephonyCallbackEvent.tryEmit(Unit) }
-            .logOutputChange(logger, "MobileSubscriptionModel")
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "MobileConnection ($subId)",
+                initialValue = state,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), state)
     }
 
@@ -243,19 +258,43 @@
                     intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
                 }
             }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "",
+                initialValue = defaultNetworkName,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
 
-    override val dataEnabled: StateFlow<Boolean> =
+    override val dataEnabled: StateFlow<Boolean> = run {
+        val initial = dataConnectionAllowed()
         telephonyPollingEvent
             .mapLatest { dataConnectionAllowed() }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "",
+                columnName = "dataEnabled",
+                initialValue = initial,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+    }
 
     private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
 
-    override val isDefaultDataSubscription: StateFlow<Boolean> =
+    override val isDefaultDataSubscription: StateFlow<Boolean> = run {
+        val initialValue = defaultDataSubId.value == subId
         defaultDataSubId
             .mapLatest { it == subId }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "",
+                columnName = "isDefaultDataSub",
+                initialValue = initialValue,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue)
+    }
 
     class Factory
     @Inject
@@ -266,6 +305,7 @@
         private val logger: ConnectivityPipelineLogger,
         private val globalSettings: GlobalSettings,
         private val mobileMappingsProxy: MobileMappingsProxy,
+        private val logFactory: TableLogBufferFactory,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
@@ -276,6 +316,8 @@
             defaultDataSubId: StateFlow<Int>,
             globalMobileDataSettingChangedEvent: Flow<Unit>,
         ): MobileConnectionRepository {
+            val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
+
             return MobileConnectionRepositoryImpl(
                 context,
                 subId,
@@ -289,8 +331,13 @@
                 mobileMappingsProxy,
                 bgDispatcher,
                 logger,
+                mobileLogger,
                 scope,
             )
         }
     }
+
+    companion object {
+        fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index a9b3d18..d407abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -120,6 +121,7 @@
                 awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
             }
             .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
+            .logInputChange(logger, "onSubscriptionsChanged")
             .onEach { infos -> dropUnusedReposFromCache(infos) }
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
 
@@ -136,6 +138,8 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .distinctUntilChanged()
+            .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
 
     private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
@@ -149,6 +153,7 @@
                 intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
             }
             .distinctUntilChanged()
+            .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
             .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
             .stateIn(
                 scope,
@@ -157,13 +162,15 @@
             )
 
     private val carrierConfigChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-        )
+        broadcastDispatcher
+            .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED))
+            .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
 
     override val defaultDataSubRatConfig: StateFlow<Config> =
         merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
             .mapLatest { Config.readConfig(context) }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultDataSubRatConfig")
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
@@ -171,10 +178,16 @@
             )
 
     override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> =
-        defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) }
+        defaultDataSubRatConfig
+            .map { mobileMappingsProxy.mapIconSets(it) }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultMobileIconMapping")
 
     override val defaultMobileIconGroup: Flow<MobileIconGroup> =
-        defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) }
+        defaultDataSubRatConfig
+            .map { mobileMappingsProxy.getDefaultIcons(it) }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultMobileIconGroup")
 
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         if (!isValidSubId(subId)) {
@@ -191,22 +204,24 @@
      * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
      * connection repositories also observe the URI for [MOBILE_DATA] + subId.
      */
-    override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
-        val observer =
-            object : ContentObserver(null) {
-                override fun onChange(selfChange: Boolean) {
-                    trySend(Unit)
-                }
+    override val globalMobileDataSettingChangedEvent: Flow<Unit> =
+        conflatedCallbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                globalSettings.registerContentObserver(
+                    globalSettings.getUriFor(MOBILE_DATA),
+                    true,
+                    observer
+                )
+
+                awaitClose { context.contentResolver.unregisterContentObserver(observer) }
             }
-
-        globalSettings.registerContentObserver(
-            globalSettings.getUriFor(MOBILE_DATA),
-            true,
-            observer
-        )
-
-        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
-    }
+            .logInputChange(logger, "globalMobileDataSettingChangedEvent")
 
     @SuppressLint("MissingPermission")
     override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -236,6 +251,8 @@
 
                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
             }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultMobileNetworkConnectivity")
             .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
 
     private fun isValidSubId(subId: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 76e6a96a..e6686dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -19,6 +19,7 @@
 import android.telephony.CarrierConfigManager
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -35,6 +36,9 @@
 import kotlinx.coroutines.flow.stateIn
 
 interface MobileIconInteractor {
+    /** The table log created for this connection */
+    val tableLogBuffer: TableLogBuffer
+
     /** The current mobile data activity */
     val activity: Flow<DataActivityModel>
 
@@ -97,6 +101,8 @@
 ) : MobileIconInteractor {
     private val connectionInfo = connectionRepository.connectionInfo
 
+    override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
+
     override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
 
     override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 62fa723..829a5ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
-import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
@@ -70,6 +69,9 @@
     private val mobileSubIdsState: StateFlow<List<Int>> =
         mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
 
+    /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
+    val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
+
     override fun start() {
         // Only notify the icon controller if we want to *render* the new icons.
         // Note that this flow may still run if
@@ -81,12 +83,4 @@
             }
         }
     }
-
-    /**
-     * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
-     * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
-     * the old view system.
-     */
-    fun createMobileIconsViewModel(): MobileIconsViewModel =
-        iconsViewModelFactory.create(mobileSubIdsState)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 545e624..ab442b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.R
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
@@ -39,7 +39,7 @@
     @JvmStatic
     fun bind(
         view: ViewGroup,
-        viewModel: MobileIconViewModel,
+        viewModel: LocationBasedMobileViewModel,
     ) {
         val activityContainer = view.requireViewById<View>(R.id.inout_container)
         val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index 0ab7bcd..e86fee2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import java.util.ArrayList
 
 class ModernStatusBarMobileView(
@@ -71,7 +71,7 @@
         fun constructAndBind(
             context: Context,
             slot: String,
-            viewModel: MobileIconViewModel,
+            viewModel: LocationBasedMobileViewModel,
         ): ModernStatusBarMobileView {
             return (LayoutInflater.from(context)
                     .inflate(R.layout.status_bar_mobile_signal_group_new, null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
new file mode 100644
index 0000000..b0dc41f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
+ * allows the mobile icon to change some view parameters at different locations
+ *
+ * @param commonImpl for convenience, this class wraps a base interface that can provides all of the
+ * common implementations between locations. See [MobileIconViewModel]
+ */
+abstract class LocationBasedMobileViewModel(
+    val commonImpl: MobileIconViewModelCommon,
+    val logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon by commonImpl {
+    abstract val tint: Flow<Int>
+
+    companion object {
+        fun viewModelForLocation(
+            commonImpl: MobileIconViewModelCommon,
+            logger: ConnectivityPipelineLogger,
+            loc: StatusBarLocation,
+        ): LocationBasedMobileViewModel =
+            when (loc) {
+                StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger)
+                StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger)
+                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger)
+            }
+    }
+}
+
+class HomeMobileIconViewModel(
+    commonImpl: MobileIconViewModelCommon,
+    logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+    override val tint: Flow<Int> =
+        flowOf(Color.CYAN)
+            .distinctUntilChanged()
+            .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})")
+}
+
+class QsMobileIconViewModel(
+    commonImpl: MobileIconViewModelCommon,
+    logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+    override val tint: Flow<Int> =
+        flowOf(Color.GREEN)
+            .distinctUntilChanged()
+            .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})")
+}
+
+class KeyguardMobileIconViewModel(
+    commonImpl: MobileIconViewModelCommon,
+    logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+    override val tint: Flow<Int> =
+        flowOf(Color.MAGENTA)
+            .distinctUntilChanged()
+            .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 961283f..2d6ac4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,23 +16,40 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
-import android.graphics.Color
 import com.android.settingslib.graph.SignalDrawable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Common interface for all of the location-based mobile icon view models. */
+interface MobileIconViewModelCommon {
+    val subscriptionId: Int
+    /** An int consumable by [SignalDrawable] for display */
+    val iconId: Flow<Int>
+    val roaming: Flow<Boolean>
+    /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+    val networkTypeIcon: Flow<Icon?>
+    val activityInVisible: Flow<Boolean>
+    val activityOutVisible: Flow<Boolean>
+    val activityContainerVisible: Flow<Boolean>
+}
 
 /**
  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -40,25 +57,29 @@
  * subscription's information.
  *
  * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
- * [MobileIconsInteractor.filteredSubscriptions]
+ * [MobileIconsInteractor.filteredSubscriptions].
  *
- * TODO: figure out where carrier merged and VCN models go (probably here?)
+ * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
+ * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
+ * model gets the exact same information, as well as allows us to log that unified state only once
+ * per icon.
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconViewModel
 constructor(
-    val subscriptionId: Int,
+    override val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     logger: ConnectivityPipelineLogger,
     constants: ConnectivityConstants,
-) {
+    scope: CoroutineScope,
+) : MobileIconViewModelCommon {
     /** Whether or not to show the error state of [SignalDrawable] */
     private val showExclamationMark: Flow<Boolean> =
         iconInteractor.isDefaultDataEnabled.mapLatest { !it }
 
-    /** An int consumable by [SignalDrawable] for display */
-    val iconId: Flow<Int> =
+    override val iconId: Flow<Int> = run {
+        val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
         combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
                 level,
                 numberOfLevels,
@@ -66,32 +87,56 @@
                 SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
             }
             .distinctUntilChanged()
-            .logOutputChange(logger, "iconId($subscriptionId)")
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "iconId",
+                initialValue = initial,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+    }
 
-    /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
-    val networkTypeIcon: Flow<Icon?> =
+    override val networkTypeIcon: Flow<Icon?> =
         combine(
-            iconInteractor.networkTypeIconGroup,
-            iconInteractor.isDataConnected,
-            iconInteractor.isDataEnabled,
-            iconInteractor.isDefaultConnectionFailed,
-            iconInteractor.alwaysShowDataRatIcon,
-        ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
-            val desc =
-                if (networkTypeIconGroup.dataContentDescription != 0)
-                    ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
-                else null
-            val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
-            return@combine when {
-                alwaysShow -> icon
-                !dataConnected -> null
-                !dataEnabled -> null
-                failedConnection -> null
-                else -> icon
+                iconInteractor.networkTypeIconGroup,
+                iconInteractor.isDataConnected,
+                iconInteractor.isDataEnabled,
+                iconInteractor.isDefaultConnectionFailed,
+                iconInteractor.alwaysShowDataRatIcon,
+            ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
+                val desc =
+                    if (networkTypeIconGroup.dataContentDescription != 0)
+                        ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+                    else null
+                val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
+                return@combine when {
+                    alwaysShow -> icon
+                    !dataConnected -> null
+                    !dataEnabled -> null
+                    failedConnection -> null
+                    else -> icon
+                }
             }
-        }
+            .distinctUntilChanged()
+            .onEach {
+                // This is done as an onEach side effect since Icon is not Diffable (yet)
+                iconInteractor.tableLogBuffer.logChange(
+                    prefix = "",
+                    columnName = "networkTypeIcon",
+                    value = it.toString(),
+                )
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
-    val roaming: Flow<Boolean> = iconInteractor.isRoaming
+    override val roaming: StateFlow<Boolean> =
+        iconInteractor.isRoaming
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "roaming",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     private val activity: Flow<DataActivityModel?> =
         if (!constants.shouldShowActivityConfig) {
@@ -100,10 +145,39 @@
             iconInteractor.activity
         }
 
-    val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false }
-    val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false }
-    val activityContainerVisible: Flow<Boolean> =
-        activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+    override val activityInVisible: Flow<Boolean> =
+        activity
+            .map { it?.hasActivityIn ?: false }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "activityInVisible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
-    val tint: Flow<Int> = flowOf(Color.CYAN)
+    override val activityOutVisible: Flow<Boolean> =
+        activity
+            .map { it?.hasActivityOut ?: false }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "activityOutVisible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val activityContainerVisible: Flow<Boolean> =
+        activity
+            .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "activityContainerVisible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 0b41d31..b9318b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
-@file:OptIn(InternalCoroutinesApi::class)
-
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import javax.inject.Inject
-import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
 
 /**
  * View model for describing the system's current mobile cellular connections. The result is a list
@@ -38,15 +40,33 @@
     private val interactor: MobileIconsInteractor,
     private val logger: ConnectivityPipelineLogger,
     private val constants: ConnectivityConstants,
+    @Application private val scope: CoroutineScope,
 ) {
-    /** TODO: do we need to cache these? */
-    fun viewModelForSub(subId: Int): MobileIconViewModel =
-        MobileIconViewModel(
-            subId,
-            interactor.createMobileConnectionInteractorForSubId(subId),
-            logger,
-            constants,
-        )
+    @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
+
+    init {
+        scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
+    }
+
+    fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
+        val common =
+            mobileIconSubIdCache[subId]
+                ?: MobileIconViewModel(
+                        subId,
+                        interactor.createMobileConnectionInteractorForSubId(subId),
+                        logger,
+                        constants,
+                        scope,
+                    )
+                    .also { mobileIconSubIdCache[subId] = it }
+
+        return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location)
+    }
+
+    private fun removeInvalidModelsFromCache(subIds: List<Int>) {
+        val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
+        subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
+    }
 
     class Factory
     @Inject
@@ -54,6 +74,7 @@
         private val interactor: MobileIconsInteractor,
         private val logger: ConnectivityPipelineLogger,
         private val constants: ConnectivityConstants,
+        @Application private val scope: CoroutineScope,
     ) {
         fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
             return MobileIconsViewModel(
@@ -61,6 +82,7 @@
                 interactor,
                 logger,
                 constants,
+                scope,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index d3cf32f..d3ff357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -18,8 +18,11 @@
 
 import android.net.Network
 import android.net.NetworkCapabilities
-import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyDisplayInfo
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
@@ -28,7 +31,9 @@
 import kotlinx.coroutines.flow.onEach
 
 @SysUISingleton
-class ConnectivityPipelineLogger @Inject constructor(
+class ConnectivityPipelineLogger
+@Inject
+constructor(
     @StatusBarConnectivityLog private val buffer: LogBuffer,
 ) {
     /**
@@ -37,34 +42,23 @@
      * Use this method for inputs that don't have any extra information besides their callback name.
      */
     fun logInputChange(callbackName: String) {
+        buffer.log(SB_LOGGING_TAG, LogLevel.INFO, { str1 = callbackName }, { "Input: $str1" })
+    }
+
+    /** Logs a change in one of the **raw inputs** to the connectivity pipeline. */
+    fun logInputChange(callbackName: String, changeInfo: String?) {
         buffer.log(
             SB_LOGGING_TAG,
             LogLevel.INFO,
-            { str1 = callbackName },
-            { "Input: $str1" }
+            {
+                str1 = callbackName
+                str2 = changeInfo
+            },
+            { "Input: $str1: $str2" }
         )
     }
 
-    /**
-     * Logs a change in one of the **raw inputs** to the connectivity pipeline.
-     */
-    fun logInputChange(callbackName: String, changeInfo: String?) {
-        buffer.log(
-                SB_LOGGING_TAG,
-                LogLevel.INFO,
-                {
-                    str1 = callbackName
-                    str2 = changeInfo
-                },
-                {
-                    "Input: $str1: $str2"
-                }
-        )
-    }
-
-    /**
-     * Logs a **data transformation** that we performed within the connectivity pipeline.
-     */
+    /** Logs a **data transformation** that we performed within the connectivity pipeline. */
     fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) {
         if (oldValue == newValue) {
             buffer.log(
@@ -74,9 +68,7 @@
                     str1 = transformationName
                     str2 = oldValue.toString()
                 },
-                {
-                    "Transform: $str1: $str2 (transformation didn't change it)"
-                }
+                { "Transform: $str1: $str2 (transformation didn't change it)" }
             )
         } else {
             buffer.log(
@@ -87,27 +79,21 @@
                     str2 = oldValue.toString()
                     str3 = newValue.toString()
                 },
-                {
-                    "Transform: $str1: $str2 -> $str3"
-                }
+                { "Transform: $str1: $str2 -> $str3" }
             )
         }
     }
 
-    /**
-     * Logs a change in one of the **outputs** to the connectivity pipeline.
-     */
+    /** Logs a change in one of the **outputs** to the connectivity pipeline. */
     fun logOutputChange(outputParamName: String, changeInfo: String) {
         buffer.log(
-                SB_LOGGING_TAG,
-                LogLevel.INFO,
-                {
-                    str1 = outputParamName
-                    str2 = changeInfo
-                },
-                {
-                    "Output: $str1: $str2"
-                }
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                str1 = outputParamName
+                str2 = changeInfo
+            },
+            { "Output: $str1: $str2" }
         )
     }
 
@@ -119,9 +105,7 @@
                 int1 = network.getNetId()
                 str1 = networkCapabilities.toString()
             },
-            {
-                "onCapabilitiesChanged: net=$int1 capabilities=$str1"
-            }
+            { "onCapabilitiesChanged: net=$int1 capabilities=$str1" }
         )
     }
 
@@ -129,21 +113,93 @@
         buffer.log(
             SB_LOGGING_TAG,
             LogLevel.INFO,
+            { int1 = network.getNetId() },
+            { "onLost: net=$int1" }
+        )
+    }
+
+    fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
             {
-                int1 = network.getNetId()
+                int1 = subId
+                bool1 = serviceState.isEmergencyOnly
+                bool2 = serviceState.roaming
+                str1 = serviceState.operatorAlphaShort
             },
             {
-                "onLost: net=$int1"
+                "onServiceStateChanged: subId=$int1 emergencyOnly=$bool1 roaming=$bool2" +
+                    " operator=$str1"
             }
         )
     }
 
+    fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                str1 = signalStrength.toString()
+            },
+            { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" }
+        )
+    }
+
+    fun logOnDataConnectionStateChanged(dataState: Int, networkType: Int, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                int2 = dataState
+                str1 = networkType.toString()
+            },
+            { "onDataConnectionStateChanged: subId=$int1 dataState=$int2 networkType=$str1" },
+        )
+    }
+
+    fun logOnDataActivity(direction: Int, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                int2 = direction
+            },
+            { "onDataActivity: subId=$int1 direction=$int2" },
+        )
+    }
+
+    fun logOnCarrierNetworkChange(active: Boolean, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                bool1 = active
+            },
+            { "onCarrierNetworkChange: subId=$int1 active=$bool1" },
+        )
+    }
+
+    fun logOnDisplayInfoChanged(displayInfo: TelephonyDisplayInfo, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                str1 = displayInfo.toString()
+            },
+            { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" },
+        )
+    }
+
     companion object {
         const val SB_LOGGING_TAG = "SbConnectivity"
 
-        /**
-         * Log a change in one of the **inputs** to the connectivity pipeline.
-         */
+        /** Log a change in one of the **inputs** to the connectivity pipeline. */
         fun Flow<Unit>.logInputChange(
             logger: ConnectivityPipelineLogger,
             inputParamName: String,
@@ -155,26 +211,26 @@
          * Log a change in one of the **inputs** to the connectivity pipeline.
          *
          * @param prettyPrint an optional function to transform the value into a readable string.
-         *   [toString] is used if no custom function is provided.
+         * [toString] is used if no custom function is provided.
          */
         fun <T> Flow<T>.logInputChange(
             logger: ConnectivityPipelineLogger,
             inputParamName: String,
             prettyPrint: (T) -> String = { it.toString() }
         ): Flow<T> {
-            return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
+            return this.onEach { logger.logInputChange(inputParamName, prettyPrint(it)) }
         }
 
         /**
          * Log a change in one of the **outputs** to the connectivity pipeline.
          *
          * @param prettyPrint an optional function to transform the value into a readable string.
-         *   [toString] is used if no custom function is provided.
+         * [toString] is used if no custom function is provided.
          */
         fun <T> Flow<T>.logOutputChange(
-                logger: ConnectivityPipelineLogger,
-                outputParamName: String,
-                prettyPrint: (T) -> String = { it.toString() }
+            logger: ConnectivityPipelineLogger,
+            outputParamName: String,
+            prettyPrint: (T) -> String = { it.toString() }
         ): Flow<T> {
             return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
new file mode 100644
index 0000000..73bcdfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 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.pipeline.wifi.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides the [WifiRepository] interface either through the [DemoWifiRepository] implementation,
+ * or the [WifiRepositoryImpl]'s prod implementation, based on the current demo mode value. In this
+ * way, downstream clients can all consist of real implementations and not care about which
+ * repository is responsible for the data. Graphically:
+ *
+ * ```
+ * RealRepository
+ *                 │
+ *                 ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ *                 │
+ * DemoRepository
+ * ```
+ *
+ * When demo mode turns on, every flow will [flatMapLatest] to the current provider's version of
+ * that flow.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class WifiRepositorySwitcher
+@Inject
+constructor(
+    private val realImpl: WifiRepositoryImpl,
+    private val demoImpl: DemoWifiRepository,
+    private val demoModeController: DemoModeController,
+    @Application scope: CoroutineScope,
+) : WifiRepository {
+    private val isDemoMode =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DemoMode {
+                        override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+                            // Don't care
+                        }
+
+                        override fun onDemoModeStarted() {
+                            demoImpl.startProcessingCommands()
+                            trySend(true)
+                        }
+
+                        override fun onDemoModeFinished() {
+                            demoImpl.stopProcessingCommands()
+                            trySend(false)
+                        }
+                    }
+
+                demoModeController.addCallback(callback)
+                awaitClose { demoModeController.removeCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+    @VisibleForTesting
+    val activeRepo =
+        isDemoMode
+            .mapLatest { isDemoMode ->
+                if (isDemoMode) {
+                    demoImpl
+                } else {
+                    realImpl
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+
+    override val isWifiEnabled: StateFlow<Boolean> =
+        activeRepo
+            .flatMapLatest { it.isWifiEnabled }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiEnabled.value)
+
+    override val isWifiDefault: StateFlow<Boolean> =
+        activeRepo
+            .flatMapLatest { it.isWifiDefault }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiDefault.value)
+
+    override val wifiNetwork: StateFlow<WifiNetworkModel> =
+        activeRepo
+            .flatMapLatest { it.wifiNetwork }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
+
+    override val wifiActivity: StateFlow<DataActivityModel> =
+        activeRepo
+            .flatMapLatest { it.wifiActivity }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
new file mode 100644
index 0000000..c588945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.pipeline.wifi.data.repository.demo
+
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** Data source to map between demo mode commands and inputs into [DemoWifiRepository]'s flows */
+@SysUISingleton
+class DemoModeWifiDataSource
+@Inject
+constructor(
+    demoModeController: DemoModeController,
+    @Application scope: CoroutineScope,
+) {
+    private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
+    private val _wifiCommands = demoCommandStream.map { args -> args.toWifiEvent() }
+    val wifiEvents = _wifiCommands.shareIn(scope, SharingStarted.WhileSubscribed())
+
+    private fun Bundle.toWifiEvent(): FakeWifiEventModel? {
+        val wifi = getString("wifi") ?: return null
+        return if (wifi == "show") {
+            activeWifiEvent()
+        } else {
+            FakeWifiEventModel.WifiDisabled
+        }
+    }
+
+    private fun Bundle.activeWifiEvent(): FakeWifiEventModel.Wifi {
+        val level = getString("level")?.toInt()
+        val activity = getString("activity")?.toActivity()
+        val ssid = getString("ssid")
+        val validated = getString("fully").toBoolean()
+
+        return FakeWifiEventModel.Wifi(
+            level = level,
+            activity = activity,
+            ssid = ssid,
+            validated = validated,
+        )
+    }
+
+    private fun String.toActivity(): Int =
+        when (this) {
+            "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
+            "in" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
+            "out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT
+            else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
new file mode 100644
index 0000000..7890074
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 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.pipeline.wifi.data.repository.demo
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/** Demo-able wifi repository to support SystemUI demo mode commands. */
+class DemoWifiRepository
+@Inject
+constructor(
+    private val dataSource: DemoModeWifiDataSource,
+    @Application private val scope: CoroutineScope,
+) : WifiRepository {
+    private var demoCommandJob: Job? = null
+
+    private val _isWifiEnabled = MutableStateFlow(false)
+    override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+
+    private val _isWifiDefault = MutableStateFlow(false)
+    override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
+
+    private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
+    override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
+
+    private val _wifiActivity =
+        MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+    override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+
+    fun startProcessingCommands() {
+        demoCommandJob =
+            scope.launch {
+                dataSource.wifiEvents.filterNotNull().collect { event -> processEvent(event) }
+            }
+    }
+
+    fun stopProcessingCommands() {
+        demoCommandJob?.cancel()
+    }
+
+    private fun processEvent(event: FakeWifiEventModel) =
+        when (event) {
+            is FakeWifiEventModel.Wifi -> processEnabledWifiState(event)
+            is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState()
+        }
+
+    private fun processDisabledWifiState() {
+        _isWifiEnabled.value = false
+        _isWifiDefault.value = false
+        _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiNetwork.value = WifiNetworkModel.Inactive
+    }
+
+    private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
+        _isWifiEnabled.value = true
+        _isWifiDefault.value = true
+        _wifiActivity.value =
+            event.activity?.toWifiDataActivityModel()
+                ?: DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiNetwork.value = event.toWifiNetworkModel()
+    }
+
+    private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
+        WifiNetworkModel.Active(
+            networkId = DEMO_NET_ID,
+            isValidated = validated ?: true,
+            level = level,
+            ssid = ssid,
+
+            // These fields below aren't supported in demo mode, since they aren't needed to satisfy
+            // the interface.
+            isPasspointAccessPoint = false,
+            isOnlineSignUpForPasspointAccessPoint = false,
+            passpointProviderFriendlyName = null,
+        )
+
+    companion object {
+        private const val DEMO_NET_ID = 1234
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
new file mode 100644
index 0000000..2353fb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.pipeline.wifi.data.repository.demo.model
+
+/**
+ * Model for demo wifi commands, ported from [NetworkControllerImpl]
+ *
+ * Nullable fields represent optional command line arguments
+ */
+sealed interface FakeWifiEventModel {
+    data class Wifi(
+        val level: Int?,
+        val activity: Int?,
+        val ssid: String?,
+        val validated: Boolean?,
+    ) : FakeWifiEventModel
+
+    object WifiDisabled : FakeWifiEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index 3c0eb91..4f7fe28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -38,16 +38,12 @@
         dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this)
     }
 
-    /** True if we should show the activityIn/activityOut icons and false otherwise. */
-    val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
-
     /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
     val alwaysShowIconIfEnabled =
         context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.apply {
-            println("shouldShowActivityConfig=$shouldShowActivityConfig")
             println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 07a7595..ab464cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -148,7 +148,7 @@
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
     private val activity: Flow<DataActivityModel?> =
-        if (!wifiConstants.shouldShowActivityConfig) {
+        if (!connectivityConstants.shouldShowActivityConfig) {
             flowOf(null)
         } else {
             combine(interactor.activity, interactor.ssid) { activity, ssid ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d8a8c5d..c9ed0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
 import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
@@ -57,7 +58,6 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
-import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -133,6 +133,7 @@
     private RevealParams mRevealParams;
     private Rect mContentBackgroundBounds;
     private boolean mIsFocusAnimationFlagActive;
+    private boolean mIsAnimatingAppearance = false;
 
     // TODO(b/193539698): move these to a Controller
     private RemoteInputController mController;
@@ -142,10 +143,6 @@
     private boolean mSending;
     private NotificationViewWrapper mWrapper;
 
-    private Integer mDefocusTargetHeight = null;
-    private boolean mIsAnimatingAppearance = false;
-
-
     // TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
     //  that need the controller shouldn't have access to the view
     private RemoteInputViewController mViewController;
@@ -423,18 +420,6 @@
         return mIsAnimatingAppearance;
     }
 
-    /**
-     * View will ensure to use at most the provided defocusTargetHeight, when defocusing animated.
-     * This is to ensure that the parent can resize itself to the targetHeight while the defocus
-     * animation of the RemoteInputView is running.
-     *
-     * @param defocusTargetHeight The target height the parent will resize itself to. If null, the
-     *                            RemoteInputView will not resize itself.
-     */
-    public void setDefocusTargetHeight(Integer defocusTargetHeight) {
-        mDefocusTargetHeight = defocusTargetHeight;
-    }
-
     @VisibleForTesting
     void onDefocus(boolean animate, boolean logClose) {
         mController.removeRemoteInput(mEntry, mToken);
@@ -443,35 +428,28 @@
         // During removal, we get reattached and lose focus. Not hiding in that
         // case to prevent flicker.
         if (!mRemoved) {
-            if (animate && mIsFocusAnimationFlagActive) {
-                Animator animator = getDefocusAnimator();
+            ViewGroup parent = (ViewGroup) getParent();
+            if (animate && parent != null && mIsFocusAnimationFlagActive) {
 
-                // When defocusing, the notification needs to shrink. Therefore, we need to free
-                // up the space that is needed for the RemoteInputView. This is done by setting
-                // a negative top margin of the height difference of the RemoteInputView and its
-                // sibling (the actions_container_layout containing the Reply button)
-                if (mDefocusTargetHeight != null && mDefocusTargetHeight < getHeight()
-                        && mDefocusTargetHeight >= 0
-                        && getLayoutParams() instanceof FrameLayout.LayoutParams) {
-                    int heightToShrink = getHeight() - mDefocusTargetHeight;
-                    FrameLayout.LayoutParams layoutParams =
-                            (FrameLayout.LayoutParams) getLayoutParams();
-                    layoutParams.topMargin = -heightToShrink;
-                    setLayoutParams(layoutParams);
-                    ((ViewGroup) getParent().getParent()).setClipChildren(false);
-                }
 
+                ViewGroup grandParent = (ViewGroup) parent.getParent();
+                ViewGroupOverlay overlay = parent.getOverlay();
+
+                // After adding this RemoteInputView to the overlay of the parent (and thus removing
+                // it from the parent itself), the parent will shrink in height. This causes the
+                // overlay to be moved. To correct the position of the overlay we need to offset it.
+                int overlayOffsetY = getMaxSiblingHeight() - getHeight();
+                overlay.add(this);
+                if (grandParent != null) grandParent.setClipChildren(false);
+
+                Animator animator = getDefocusAnimator(overlayOffsetY);
+                View self = this;
                 animator.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        //reset top margin after the animation
-                        if (getLayoutParams() instanceof FrameLayout.LayoutParams) {
-                            FrameLayout.LayoutParams layoutParams =
-                                    (FrameLayout.LayoutParams) getLayoutParams();
-                            layoutParams.topMargin = 0;
-                            setLayoutParams(layoutParams);
-                            ((ViewGroup) getParent().getParent()).setClipChildren(true);
-                        }
+                        overlay.remove(self);
+                        parent.addView(self);
+                        if (grandParent != null) grandParent.setClipChildren(true);
                         setVisibility(GONE);
                         if (mWrapper != null) {
                             mWrapper.setRemoteInputVisible(false);
@@ -609,7 +587,7 @@
     }
 
     /**
-     * Sets whether the feature flag for the updated inline reply animation is active or not.
+     * Sets whether the feature flag for the revised inline reply animation is active or not.
      * @param active
      */
     public void setIsFocusAnimationFlagActive(boolean active) {
@@ -846,6 +824,23 @@
         }
     }
 
+    /**
+     * @return max sibling height (0 in case of no siblings)
+     */
+    public int getMaxSiblingHeight() {
+        ViewGroup parentView = (ViewGroup) getParent();
+        int maxHeight = 0;
+        if (parentView == null) return 0;
+        for (int i = 0; i < parentView.getChildCount(); i++) {
+            View siblingView = parentView.getChildAt(i);
+            if (siblingView != this) maxHeight = Math.max(maxHeight, siblingView.getHeight());
+        }
+        return maxHeight;
+    }
+
+    /**
+     * Creates an animator for the focus animation.
+     */
     private Animator getFocusAnimator(View crossFadeView) {
         final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
         alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
@@ -854,7 +849,7 @@
 
         ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
         scaleAnimator.addUpdateListener(valueAnimator -> {
-            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), 0);
         });
         scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
         scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
@@ -875,21 +870,26 @@
         return animatorSet;
     }
 
-    private Animator getDefocusAnimator() {
+    /**
+     * Creates an animator for the defocus animation.
+     *
+     * @param offsetY The RemoteInputView will be offset by offsetY during the animation
+     */
+    private Animator getDefocusAnimator(int offsetY) {
         final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
         alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
         alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
 
         ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
         scaleAnimator.addUpdateListener(valueAnimator -> {
-            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), offsetY);
         });
         scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
         scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
         scaleAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation, boolean isReverse) {
-                setFocusAnimationScaleY(1f);
+                setFocusAnimationScaleY(1f /* scaleY */, 0 /* verticalOffset */);
             }
         });
 
@@ -901,15 +901,21 @@
     /**
      * Sets affected view properties for a vertical scale animation
      *
-     * @param scaleY desired vertical view scale
+     * @param scaleY         desired vertical view scale
+     * @param verticalOffset vertical offset to apply to the RemoteInputView during the animation
      */
-    private void setFocusAnimationScaleY(float scaleY) {
+    private void setFocusAnimationScaleY(float scaleY, int verticalOffset) {
         int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
-        mContentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
+        Rect contentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
                 mContentView.getHeight() - verticalBoundOffset);
-        mContentBackground.setBounds(mContentBackgroundBounds);
+        mContentBackground.setBounds(contentBackgroundBounds);
         mContentView.setBackground(mContentBackground);
-        setTranslationY(verticalBoundOffset);
+        if (scaleY == 1f) {
+            mContentBackgroundBounds = null;
+        } else {
+            mContentBackgroundBounds = contentBackgroundBounds;
+        }
+        setTranslationY(verticalBoundOffset + verticalOffset);
     }
 
     /** Handler for button click on send action in IME. */
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 1f44434..2464886 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -33,6 +33,7 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeAvailabilityTracker;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.util.settings.GlobalSettings;
 
 public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
 
@@ -54,13 +55,15 @@
     private SwitchPreference mOnSwitch;
 
     private DemoModeController mDemoModeController;
+    private GlobalSettings mGlobalSettings;
     private Tracker mDemoModeTracker;
 
     // We are the only ones who ever call this constructor, so don't worry about the warning
     @SuppressLint("ValidFragment")
-    public DemoModeFragment(DemoModeController demoModeController) {
+    public DemoModeFragment(DemoModeController demoModeController, GlobalSettings globalSettings) {
         super();
         mDemoModeController = demoModeController;
+        mGlobalSettings = globalSettings;
     }
 
 
@@ -80,7 +83,7 @@
         screen.addPreference(mOnSwitch);
         setPreferenceScreen(screen);
 
-        mDemoModeTracker = new Tracker(context);
+        mDemoModeTracker = new Tracker(context, mGlobalSettings);
         mDemoModeTracker.startTracking();
         updateDemoModeEnabled();
         updateDemoModeOn();
@@ -202,8 +205,8 @@
     }
 
     private class Tracker extends DemoModeAvailabilityTracker {
-        Tracker(Context context) {
-            super(context);
+        Tracker(Context context, GlobalSettings globalSettings) {
+            super(context, globalSettings);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 3231aec..32ecb67 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -33,6 +33,7 @@
 import com.android.systemui.R;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.util.settings.GlobalSettings;
 
 import javax.inject.Inject;
 
@@ -44,12 +45,18 @@
 
     private final DemoModeController mDemoModeController;
     private final TunerService mTunerService;
+    private final GlobalSettings mGlobalSettings;
 
     @Inject
-    TunerActivity(DemoModeController demoModeController, TunerService tunerService) {
+    TunerActivity(
+            DemoModeController demoModeController,
+            TunerService tunerService,
+            GlobalSettings globalSettings
+    ) {
         super();
         mDemoModeController = demoModeController;
         mTunerService = tunerService;
+        mGlobalSettings = globalSettings;
     }
 
     protected void onCreate(Bundle savedInstanceState) {
@@ -69,7 +76,7 @@
             boolean showDemoMode = action != null && action.equals(
                     "com.android.settings.action.DEMO_MODE");
             final PreferenceFragment fragment = showDemoMode
-                    ? new DemoModeFragment(mDemoModeController)
+                    ? new DemoModeFragment(mDemoModeController, mGlobalSettings)
                     : new TunerFragment(mTunerService);
             getFragmentManager().beginTransaction().replace(R.id.content_frame,
                     fragment, TAG_TUNER).commit();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 6c1f008..bb03f28 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -22,9 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -33,7 +37,6 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 /**
@@ -50,7 +53,9 @@
 
     @Mock private lateinit var parent: ViewGroup
 
-    private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    private lateinit var underTest: KeyguardUnfoldTransition
     private lateinit var progressListener: TransitionProgressListener
     private var xTranslationMax = 0f
 
@@ -61,10 +66,10 @@
         xTranslationMax =
             context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
 
-        keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
+        underTest = KeyguardUnfoldTransition(context, statusBarStateController, progressProvider)
 
-        keyguardUnfoldTransition.setup(parent)
-        keyguardUnfoldTransition.statusViewCentered = false
+        underTest.setup(parent)
+        underTest.statusViewCentered = false
 
         verify(progressProvider).addCallback(capture(progressListenerCaptor))
         progressListener = progressListenerCaptor.value
@@ -72,10 +77,11 @@
 
     @Test
     fun onTransition_centeredViewDoesNotMove() {
-        keyguardUnfoldTransition.statusViewCentered = true
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+        underTest.statusViewCentered = true
 
         val view = View(context)
-        `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+        whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
 
         progressListener.onTransitionStarted()
         assertThat(view.translationX).isZero()
@@ -89,4 +95,44 @@
         progressListener.onTransitionFinished()
         assertThat(view.translationX).isZero()
     }
+
+    @Test
+    fun whenInShadeState_viewDoesNotMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInKeyguardState_viewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 898f370..b4696e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,8 +35,6 @@
 import android.view.WindowManager
 import android.widget.ScrollView
 import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
-import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.R
@@ -81,6 +79,8 @@
     @Mock
     lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock
+    lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector
+    @Mock
     lateinit var windowToken: IBinder
     @Mock
     lateinit var interactionJankMonitor: InteractionJankMonitor
@@ -170,26 +170,6 @@
     }
 
     @Test
-    fun testDismissesOnFocusLoss() {
-        val container = initializeFingerprintContainer()
-        waitForIdleSync()
-
-        val requestID = authContainer?.requestId ?: 0L
-
-        verify(callback).onDialogAnimatedIn(requestID)
-
-        container.onWindowFocusChanged(false)
-        waitForIdleSync()
-
-        verify(callback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(requestID)
-        )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
     fun testFocusLossAfterRotating() {
         val container = initializeFingerprintContainer()
         waitForIdleSync()
@@ -209,35 +189,6 @@
     }
 
     @Test
-    fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
-        waitForIdleSync()
-
-        val requestID = authContainer?.requestId ?: 0L
-
-        // Simulate keyboard was shown on the credential view
-        val windowInsetsController = container.windowInsetsController
-        spyOn(windowInsetsController)
-        spyOn(container.rootWindowInsets)
-        doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
-
-        container.onWindowFocusChanged(false)
-        waitForIdleSync()
-
-        // Expect hiding IME request will be invoked when dismissing the view
-        verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
-
-        verify(callback).onDismissed(
-            eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-            eq<ByteArray?>(null), /* credentialAttestation */
-            eq(requestID)
-        )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
         container.mBiometricCallback.onAction(
@@ -519,6 +470,7 @@
         fingerprintProps,
         faceProps,
         wakefulnessLifecycle,
+        panelInteractionDetector,
         userManager,
         lockPatternUtils,
         interactionJankMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 67b293f44..5afe49e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -152,6 +152,8 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
+    private AuthDialogPanelInteractionDetector mPanelInteractionDetector;
+    @Mock
     private UserManager mUserManager;
     @Mock
     private LockPatternUtils mLockPatternUtils;
@@ -953,9 +955,10 @@
             super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
                     mFingerprintManager, mFaceManager, () -> mUdfpsController,
                     () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
-                    mUserManager, mLockPatternUtils, mUdfpsLogger, mLogContextInteractor,
-                    () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
-                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper);
+                    mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
+                    mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
+                    () -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
+                    mBackgroundExecutor, mVibratorHelper);
         }
 
         @Override
@@ -963,7 +966,9 @@
                 boolean requireConfirmation, int userId, int[] sensorIds,
                 String opPackageName, boolean skipIntro, long operationId, long requestId,
                 @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
-                WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager,
+                WakefulnessLifecycle wakefulnessLifecycle,
+                AuthDialogPanelInteractionDetector panelInteractionDetector,
+                UserManager userManager,
                 LockPatternUtils lockPatternUtils) {
 
             mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index e7e6918..bdd496e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
 
+import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -32,6 +34,7 @@
 import android.content.ClipboardManager;
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -66,6 +69,8 @@
     @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
+    private ClipboardToast mClipboardToast;
+    @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
     private FeatureFlags mFeatureFlags;
@@ -84,6 +89,8 @@
     @Spy
     private Provider<ClipboardOverlayController> mOverlayControllerProvider;
 
+    private ClipboardListener mClipboardListener;
+
 
     @Before
     public void setup() {
@@ -93,7 +100,8 @@
         when(mClipboardOverlayControllerLegacyFactory.create(any()))
                 .thenReturn(mOverlayControllerLegacy);
         when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);
 
         mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                 new ClipData.Item("Test Item"));
@@ -101,16 +109,17 @@
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
         mDeviceConfigProxy = new DeviceConfigProxyFake();
+
+        mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags);
     }
 
     @Test
     public void test_disabled() {
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "false", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
         verifyZeroInteractions(mClipboardManager);
         verifyZeroInteractions(mUiEventLogger);
     }
@@ -119,10 +128,7 @@
     public void test_enabled() {
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
         verify(mClipboardManager).addPrimaryClipChangedListener(any());
         verifyZeroInteractions(mUiEventLogger);
     }
@@ -133,11 +139,8 @@
 
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mClipboardOverlayControllerLegacyFactory).create(any());
 
@@ -152,14 +155,14 @@
         // Should clear the overlay controller
         mRunnableCaptor.getValue().run();
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
 
         // Not calling the runnable here, just change the clip again and verify that the overlay is
         // NOT recreated.
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
         verifyZeroInteractions(mOverlayControllerProvider);
@@ -171,11 +174,8 @@
 
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider).get();
 
@@ -190,14 +190,14 @@
         // Should clear the overlay controller
         mRunnableCaptor.getValue().run();
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider, times(2)).get();
 
         // Not calling the runnable here, just change the clip again and verify that the overlay is
         // NOT recreated.
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider, times(2)).get();
         verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
@@ -233,13 +233,10 @@
     public void test_logging_enterAndReenter() {
         when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
 
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
 
-        listener.onPrimaryClipChanged();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mUiEventLogger, times(1)).log(
                 ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
@@ -251,17 +248,29 @@
     public void test_logging_enterAndReenter_new() {
         when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
 
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
 
-        listener.onPrimaryClipChanged();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mUiEventLogger, times(1)).log(
                 ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
         verify(mUiEventLogger, times(1)).log(
                 ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
     }
+
+    @Test
+    public void test_userSetupIncomplete_showsToast() {
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+        verify(mClipboardToast, times(1)).showCopiedToast();
+        verifyZeroInteractions(mOverlayControllerProvider);
+        verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
new file mode 100644
index 0000000..87c66b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.demomode
+
+import android.content.Intent
+import android.os.Bundle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode.ACTION_DEMO
+import com.android.systemui.demomode.DemoMode.COMMAND_STATUS
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class DemoModeControllerTest : SysuiTestCase() {
+    private lateinit var underTest: DemoModeController
+
+    @Mock private lateinit var dumpManager: DumpManager
+
+    private val globalSettings = FakeSettings()
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+
+        MockitoAnnotations.initMocks(this)
+
+        globalSettings.putInt(DemoModeController.DEMO_MODE_ALLOWED, 1)
+        globalSettings.putInt(DemoModeController.DEMO_MODE_ON, 1)
+
+        underTest =
+            DemoModeController(
+                context = context,
+                dumpManager = dumpManager,
+                globalSettings = globalSettings,
+                broadcastDispatcher = fakeBroadcastDispatcher
+            )
+
+        underTest.initialize()
+    }
+
+    @Test
+    fun `demo command flow - returns args`() =
+        testScope.runTest {
+            var latest: Bundle? = null
+            val flow = underTest.demoFlowForCommand(TEST_COMMAND)
+            val job = launch { flow.collect { latest = it } }
+
+            sendDemoCommand(args = mapOf("key1" to "val1"))
+            assertThat(latest!!.getString("key1")).isEqualTo("val1")
+
+            sendDemoCommand(args = mapOf("key2" to "val2"))
+            assertThat(latest!!.getString("key2")).isEqualTo("val2")
+
+            job.cancel()
+        }
+
+    private fun sendDemoCommand(command: String? = TEST_COMMAND, args: Map<String, String>) {
+        val intent = Intent(ACTION_DEMO)
+        intent.putExtra("command", command)
+        args.forEach { arg -> intent.putExtra(arg.key, arg.value) }
+
+        fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive(context, intent) }
+    }
+
+    companion object {
+        // Use a valid command until we properly fake out everything
+        const val TEST_COMMAND = COMMAND_STATUS
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
deleted file mode 100644
index 003efbf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 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.dreams
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class DreamCallbackControllerTest : SysuiTestCase() {
-
-    @Mock private lateinit var callback: DreamCallbackController.DreamCallback
-
-    private lateinit var underTest: DreamCallbackController
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        underTest = DreamCallbackController()
-    }
-
-    @Test
-    fun testOnWakeUpInvokesCallback() {
-        underTest.addCallback(callback)
-        underTest.onWakeUp()
-        verify(callback).onWakeUp()
-
-        // Adding twice should not invoke twice
-        reset(callback)
-        underTest.addCallback(callback)
-        underTest.onWakeUp()
-        verify(callback, times(1)).onWakeUp()
-
-        // After remove, no call to callback
-        reset(callback)
-        underTest.removeCallback(callback)
-        underTest.onWakeUp()
-        verify(callback, never()).onWakeUp()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
new file mode 100644
index 0000000..9f534ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.dreams
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayCallbackControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var callback: DreamOverlayCallbackController.Callback
+
+    private lateinit var underTest: DreamOverlayCallbackController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = DreamOverlayCallbackController()
+    }
+
+    @Test
+    fun onWakeUpInvokesCallback() {
+        underTest.onStartDream()
+        assertThat(underTest.isDreaming).isEqualTo(true)
+
+        underTest.addCallback(callback)
+        underTest.onWakeUp()
+        verify(callback).onWakeUp()
+        assertThat(underTest.isDreaming).isEqualTo(false)
+
+        // Adding twice should not invoke twice
+        reset(callback)
+        underTest.addCallback(callback)
+        underTest.onWakeUp()
+        verify(callback, times(1)).onWakeUp()
+
+        // After remove, no call to callback
+        reset(callback)
+        underTest.removeCallback(callback)
+        underTest.onWakeUp()
+        verify(callback, never()).onWakeUp()
+    }
+
+    @Test
+    fun onStartDreamInvokesCallback() {
+        underTest.addCallback(callback)
+
+        assertThat(underTest.isDreaming).isEqualTo(false)
+
+        underTest.onStartDream()
+        verify(callback).onStartDream()
+        assertThat(underTest.isDreaming).isEqualTo(true)
+
+        // Adding twice should not invoke twice
+        reset(callback)
+        underTest.addCallback(callback)
+        underTest.onStartDream()
+        verify(callback, times(1)).onStartDream()
+
+        // After remove, no call to callback
+        reset(callback)
+        underTest.removeCallback(callback)
+        underTest.onStartDream()
+        verify(callback, never()).onStartDream()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 52663ce..8f97026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -140,7 +140,7 @@
     UiEventLogger mUiEventLogger;
 
     @Mock
-    DreamCallbackController mDreamCallbackController;
+    DreamOverlayCallbackController mDreamOverlayCallbackController;
 
     @Captor
     ArgumentCaptor<View> mViewCaptor;
@@ -186,7 +186,7 @@
                 mUiEventLogger,
                 mTouchInsetManager,
                 LOW_LIGHT_COMPONENT,
-                mDreamCallbackController);
+                mDreamOverlayCallbackController);
     }
 
     @Test
@@ -398,7 +398,7 @@
         mService.onWakeUp(callback);
         mMainExecutor.runAllReady();
         verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
-        verify(mDreamCallbackController).onWakeUp();
+        verify(mDreamOverlayCallbackController).onWakeUp();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 563d44d3..be712f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -28,8 +28,7 @@
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
-import com.android.systemui.dreams.DreamCallbackController
-import com.android.systemui.dreams.DreamCallbackController.DreamCallback
+import com.android.systemui.dreams.DreamOverlayCallbackController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -68,7 +67,7 @@
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var dreamCallbackController: DreamCallbackController
+    @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -86,7 +85,7 @@
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
                 authController,
-                dreamCallbackController,
+                dreamOverlayCallbackController,
             )
     }
 
@@ -171,6 +170,29 @@
         }
 
     @Test
+    fun isKeyguardOccluded() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isOccluded).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
+
+            whenever(keyguardStateController.isOccluded).thenReturn(true)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isTrue()
+
+            whenever(keyguardStateController.isOccluded).thenReturn(false)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun isDozing() =
         runTest(UnconfinedTestDispatcher()) {
             var latest: Boolean? = null
@@ -343,19 +365,22 @@
         }
 
     @Test
-    fun isDreamingFromDreamCallbackController() =
+    fun isDreamingFromDreamOverlayCallbackController() =
         runTest(UnconfinedTestDispatcher()) {
-            whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(true)
+            whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
             var latest: Boolean? = null
-            val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+            val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
 
-            assertThat(latest).isTrue()
+            assertThat(latest).isFalse()
 
             val listener =
-                withArgCaptor<DreamCallbackController.DreamCallback> {
-                    verify(dreamCallbackController).addCallback(capture())
+                withArgCaptor<DreamOverlayCallbackController.Callback> {
+                    verify(dreamOverlayCallbackController).addCallback(capture())
                 }
 
+            listener.onStartDream()
+            assertThat(latest).isTrue()
+
             listener.onWakeUp()
             assertThat(latest).isFalse()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a6cf840..d2b7838 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -42,6 +44,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mock
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -64,8 +67,8 @@
     // Used to verify transition requests for test output
     @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
 
-    private lateinit var lockscreenBouncerTransitionInteractor:
-        LockscreenBouncerTransitionInteractor
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
 
     @Before
     fun setUp() {
@@ -79,25 +82,82 @@
         transitionRepository = KeyguardTransitionRepositoryImpl()
         runner = KeyguardTransitionRunner(transitionRepository)
 
-        lockscreenBouncerTransitionInteractor =
-            LockscreenBouncerTransitionInteractor(
+        fromLockscreenTransitionInteractor =
+            FromLockscreenTransitionInteractor(
                 scope = testScope,
                 keyguardInteractor = KeyguardInteractor(keyguardRepository),
                 shadeRepository = shadeRepository,
                 keyguardTransitionRepository = mockTransitionRepository,
                 keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
             )
-        lockscreenBouncerTransitionInteractor.start()
+        fromLockscreenTransitionInteractor.start()
+
+        fromDreamingTransitionInteractor =
+            FromDreamingTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = KeyguardInteractor(keyguardRepository),
+                keyguardTransitionRepository = mockTransitionRepository,
+                keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+            )
+        fromDreamingTransitionInteractor.start()
     }
 
     @Test
+    fun `DREAMING to LOCKSCREEN`() =
+        testScope.runTest {
+            // GIVEN a device is dreaming and occluded
+            keyguardRepository.setDreamingWithOverlay(true)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DREAMING,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN doze is complete
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            // AND dreaming has stopped
+            keyguardRepository.setDreamingWithOverlay(false)
+            // AND occluded has stopped
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to BOUNCER should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun `LOCKSCREEN to BOUNCER via bouncer showing call`() =
         testScope.runTest {
             // GIVEN a device that has at least woken up
             keyguardRepository.setWakefulnessModel(startingToWake())
             runCurrent()
 
-            // GIVEN a transition has run to LOCKSCREEN
+            // GIVEN a prior transition has run to LOCKSCREEN
             runner.startTransition(
                 testScope,
                 TransitionInfo(
@@ -122,7 +182,7 @@
                     verify(mockTransitionRepository).startTransition(capture())
                 }
             // THEN a transition to BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor")
+            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
             assertThat(info.to).isEqualTo(KeyguardState.BOUNCER)
             assertThat(info.animator).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c54e456..5571663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
new file mode 100644
index 0000000..db6fc13
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed. This is controlled by
+ * the set of ids, which also dictact which direction to move and when, via a filter fn.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationPanelUnfoldAnimationControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+
+    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+    @Mock private lateinit var parent: ViewGroup
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    private lateinit var underTest: NotificationPanelUnfoldAnimationController
+    private lateinit var progressListener: TransitionProgressListener
+    private var xTranslationMax = 0f
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        xTranslationMax =
+            context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+
+        underTest =
+            NotificationPanelUnfoldAnimationController(
+                context,
+                statusBarStateController,
+                progressProvider
+            )
+        underTest.setup(parent)
+
+        verify(progressProvider).addCallback(capture(progressListenerCaptor))
+        progressListener = progressListenerCaptor.value
+    }
+
+    @Test
+    fun whenInKeyguardState_viewDoesNotMove() {
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInShadeState_viewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInShadeLockedState_viewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE_LOCKED)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 43c6942..3e769e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -21,6 +21,7 @@
 import android.provider.Settings.Secure.DOZE_TAP_SCREEN_GESTURE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.os.PowerManager
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,9 +37,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyObject
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.never
@@ -106,7 +107,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces).wakeUpIfDozing(
+                anyLong(), any(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
     }
 
     @Test
@@ -125,7 +127,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces).wakeUpIfDozing(
+                anyLong(), any(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
     }
 
     @Test
@@ -156,7 +159,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     @Test
@@ -203,7 +207,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     @Test
@@ -222,7 +227,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     @Test
@@ -241,7 +247,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     fun updateSettings() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 1ce460c..3a1f9b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -177,6 +178,8 @@
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -189,8 +192,6 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 
-import dagger.Lazy;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -306,6 +307,7 @@
     @Mock private ViewRootImpl mViewRootImpl;
     @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+    @Mock IPowerManager mPowerManagerService;
 
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -319,9 +321,8 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        IPowerManager powerManagerService = mock(IPowerManager.class);
         IThermalService thermalService = mock(IThermalService.class);
-        mPowerManager = new PowerManager(mContext, powerManagerService, thermalService,
+        mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
                 Handler.createAsync(Looper.myLooper()));
 
         mNotificationInterruptStateProvider =
@@ -363,7 +364,7 @@
         when(mStackScrollerController.getView()).thenReturn(mStackScroller);
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
-        when(powerManagerService.isInteractive()).thenReturn(true);
+        when(mPowerManagerService.isInteractive()).thenReturn(true);
         when(mStackScroller.getActivatedChild()).thenReturn(null);
 
         doAnswer(invocation -> {
@@ -1190,6 +1191,34 @@
         verify(mStatusBarStateController).setState(SHADE);
     }
 
+    @Test
+    public void dozing_wakeUp() throws RemoteException {
+        // GIVEN can wakeup when dozing & is dozing
+        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
+        setDozing(true);
+
+        // WHEN wakeup is requested
+        final int wakeReason = PowerManager.WAKE_REASON_TAP;
+        mCentralSurfaces.wakeUpIfDozing(0, null, "", wakeReason);
+
+        // THEN power manager receives wakeup
+        verify(mPowerManagerService).wakeUp(eq(0L), eq(wakeReason), anyString(), anyString());
+    }
+
+    @Test
+    public void notDozing_noWakeUp() throws RemoteException {
+        // GIVEN can wakeup when dozing and NOT dozing
+        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
+        setDozing(false);
+
+        // WHEN wakeup is requested
+        final int wakeReason = PowerManager.WAKE_REASON_TAP;
+        mCentralSurfaces.wakeUpIfDozing(0, null, "", wakeReason);
+
+        // THEN power manager receives wakeup
+        verify(mPowerManagerService, never()).wakeUp(anyLong(), anyInt(), anyString(), anyString());
+    }
+
     /**
      * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
      * to reconfigure the keyguard to reflect the requested showing/occluded states.
@@ -1226,6 +1255,13 @@
                 states);
     }
 
+    private void setDozing(boolean isDozing) {
+        ArgumentCaptor<StatusBarStateController.StateListener> callbackCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(mStatusBarStateController).addCallback(callbackCaptor.capture(), anyInt());
+        callbackCaptor.getValue().onDozingChanged(isDozing);
+    }
+
     public static class TestableNotificationInterruptStateProviderImpl extends
             NotificationInterruptStateProviderImpl {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
new file mode 100644
index 0000000..f822ba0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 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.pipeline.mobile.data.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class MobileConnectionModelTest : SysuiTestCase() {
+
+    @Test
+    fun `log diff - initial log contains all columns`() {
+        val logger = TestLogger()
+        val connection = MobileConnectionModel()
+
+        connection.logFull(logger)
+
+        assertThat(logger.changes)
+            .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
+        assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
+        assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
+        assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_ACTIVITY_DIRECTION, connection.dataActivityDirection.toString()))
+        assertThat(logger.changes)
+            .contains(
+                Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
+            )
+        assertThat(logger.changes)
+            .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
+    }
+
+    @Test
+    fun `log diff - primary level changes - only level is logged`() {
+        val logger = TestLogger()
+        val connectionOld = MobileConnectionModel(primaryLevel = 1)
+
+        val connectionNew = MobileConnectionModel(primaryLevel = 2)
+
+        connectionNew.logDiffs(connectionOld, logger)
+
+        assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
+    }
+
+    private class TestLogger : TableRowLogger {
+        val changes = mutableListOf<Pair<String, String>>()
+
+        override fun logChange(columnName: String, value: String?) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+
+        override fun logChange(columnName: String, value: Int) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+
+        override fun logChange(columnName: String, value: Boolean) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 59eec53..d6a9ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -16,12 +16,16 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
-class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+class FakeMobileConnectionRepository(
+    override val subId: Int,
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepository {
     private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
     override val connectionInfo = _connectionInfo
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 04d3cdd..7f93328 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -22,14 +22,17 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
-class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
-    MobileConnectionsRepository {
+class FakeMobileConnectionsRepository(
+    mobileMappings: MobileMappingsProxy,
+    val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionsRepository {
     val GSM_KEY = mobileMappings.toIconKey(GSM)
     val LTE_KEY = mobileMappings.toIconKey(LTE)
     val UMTS_KEY = mobileMappings.toIconKey(UMTS)
@@ -63,7 +66,7 @@
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         return subIdRepos[subId]
-            ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
+            ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
     }
 
     private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 18ae90d..5d377a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
@@ -37,6 +39,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -69,12 +72,14 @@
     private lateinit var realRepo: MobileConnectionsRepositoryImpl
     private lateinit var demoRepo: DemoMobileConnectionsRepository
     private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var logFactory: TableLogBufferFactory
 
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var demoModeController: DemoModeController
+    @Mock private lateinit var dumpManager: DumpManager
 
     private val globalSettings = FakeSettings()
     private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
@@ -86,6 +91,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
 
@@ -114,6 +121,7 @@
                 dataSource = mockDataSource,
                 scope = scope,
                 context = context,
+                logFactory = logFactory,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 3d5316d..2102085 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
@@ -54,6 +56,9 @@
 @RunWith(Parameterized::class)
 internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
     SysuiTestCase() {
+
+    private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -76,6 +81,7 @@
                 dataSource = mockDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
+                logFactory = logFactory,
             )
 
         connectionsRepo.startProcessingCommands()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 34f30eb..cdbe75e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -23,6 +23,8 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons.THREE_G
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -32,6 +34,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +50,9 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
+    private val dumpManager: DumpManager = mock()
+    private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -68,6 +74,7 @@
                 dataSource = mockDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
+                logFactory = logFactory,
             )
 
         underTest.startProcessingCommands()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 7fa8065..7970443 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -50,6 +50,7 @@
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -87,14 +88,15 @@
 @SmallTest
 class MobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionRepositoryImpl
+    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
 
     private val scope = CoroutineScope(IMMEDIATE)
     private val mobileMappings = FakeMobileMappingsProxy()
     private val globalSettings = FakeSettings()
-    private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
 
     @Before
     fun setUp() {
@@ -102,6 +104,8 @@
         globalSettings.userId = UserHandle.USER_ALL
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
+        connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
+
         underTest =
             MobileConnectionRepositoryImpl(
                 context,
@@ -116,6 +120,7 @@
                 mobileMappings,
                 IMMEDIATE,
                 logger,
+                tableLogger,
                 scope,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 3cc1e8b..b8cd7a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -34,12 +34,15 @@
 import com.android.settingslib.R
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
@@ -57,6 +60,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -72,6 +76,7 @@
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var logBufferFactory: TableLogBufferFactory
 
     private val mobileMappings = FakeMobileMappingsProxy()
 
@@ -89,6 +94,10 @@
             }
         }
 
+        whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ ->
+            mock<TableLogBuffer>()
+        }
+
         connectionFactory =
             MobileConnectionRepositoryImpl.Factory(
                 fakeBroadcastDispatcher,
@@ -99,6 +108,7 @@
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
                 scope = scope,
+                logFactory = logBufferFactory,
             )
 
         underTest =
@@ -271,6 +281,32 @@
         }
 
     @Test
+    fun `connection repository - log buffer contains sub id in its name`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger creation
+            underTest.getRepoForSubId(SUB_1_ID)
+            verify(logBufferFactory)
+                .create(
+                    eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)),
+                    anyInt(),
+                )
+            underTest.getRepoForSubId(SUB_2_ID)
+            verify(logBufferFactory)
+                .create(
+                    eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)),
+                    anyInt(),
+                )
+
+            job.cancel()
+        }
+
+    @Test
     fun testDefaultDataSubId_updatesOnBroadcast() =
         runBlocking(IMMEDIATE) {
             var latest: Int? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index c3519b7..c494589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -19,11 +19,14 @@
 import android.telephony.CellSignalStrength
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileIconInteractor : MobileIconInteractor {
+class FakeMobileIconInteractor(
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileIconInteractor {
     override val alwaysShowDataRatIcon = MutableStateFlow(false)
 
     override val activity =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 9f300e9..19e5516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -22,12 +22,15 @@
 import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
+class FakeMobileIconsInteractor(
+    mobileMappings: MobileMappingsProxy,
+    val tableLogBuffer: TableLogBuffer,
+) : MobileIconsInteractor {
     val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
     val LTE_KEY = mobileMappings.toIconKey(LTE)
     val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -48,8 +51,7 @@
 
     override val isDefaultConnectionFailed = MutableStateFlow(false)
 
-    private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
-    override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
+    override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
 
     private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
     override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
@@ -67,7 +69,7 @@
 
     /** Always returns a new fake interactor */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
-        return FakeMobileIconInteractor()
+        return FakeMobileIconInteractor(tableLogBuffer)
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 4dca780..83c5055 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -49,8 +49,8 @@
 class MobileIconInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconInteractor
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
-    private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
+    private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+    private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
 
     private val scope = CoroutineScope(IMMEDIATE)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 8557894..2fa3467 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
@@ -28,6 +29,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -44,9 +46,9 @@
 @SmallTest
 class MobileIconsInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsInteractor
+    private lateinit var connectionsRepository: FakeMobileConnectionsRepository
     private val userSetupRepository = FakeUserSetupRepository()
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
     private val scope = CoroutineScope(IMMEDIATE)
 
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -55,6 +57,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
         connectionsRepository.setMobileConnectionRepositoryMap(
             mapOf(
                 SUB_1_ID to CONNECTION_1,
@@ -290,21 +293,23 @@
 
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
+        private val tableLogBuffer =
+            TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
 
         private const val SUB_1_ID = 1
         private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
-        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
+        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
 
         private const val SUB_2_ID = 2
         private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
-        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
+        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
 
         private const val SUB_3_ID = 3
         private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
-        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
+        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
 
         private const val SUB_4_ID = 4
         private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
-        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
+        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
new file mode 100644
index 0000000..043d55a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
+    private lateinit var commonImpl: MobileIconViewModelCommon
+    private lateinit var homeIcon: HomeMobileIconViewModel
+    private lateinit var qsIcon: QsMobileIconViewModel
+    private lateinit var keyguardIcon: KeyguardMobileIconViewModel
+    private lateinit var interactor: FakeMobileIconInteractor
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var constants: ConnectivityConstants
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        interactor = FakeMobileIconInteractor(tableLogBuffer)
+        interactor.apply {
+            setLevel(1)
+            setIsDefaultDataEnabled(true)
+            setIsFailedConnection(false)
+            setIconGroup(TelephonyIcons.THREE_G)
+            setIsEmergencyOnly(false)
+            setNumberOfLevels(4)
+            isDataConnected.value = true
+        }
+        commonImpl =
+            MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+
+        homeIcon = HomeMobileIconViewModel(commonImpl, logger)
+        qsIcon = QsMobileIconViewModel(commonImpl, logger)
+        keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger)
+    }
+
+    @Test
+    fun `location based view models receive same icon id when common impl updates`() =
+        testScope.runTest {
+            var latestHome: Int? = null
+            val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
+
+            var latestQs: Int? = null
+            val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
+
+            var latestKeyguard: Int? = null
+            val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
+
+            var expected = defaultSignal(level = 1)
+
+            assertThat(latestHome).isEqualTo(expected)
+            assertThat(latestQs).isEqualTo(expected)
+            assertThat(latestKeyguard).isEqualTo(expected)
+
+            interactor.setLevel(2)
+            expected = defaultSignal(level = 2)
+
+            assertThat(latestHome).isEqualTo(expected)
+            assertThat(latestQs).isEqualTo(expected)
+            assertThat(latestKeyguard).isEqualTo(expected)
+
+            homeJob.cancel()
+            qsJob.cancel()
+            keyguardJob.cancel()
+        }
+
+    companion object {
+        private const val SUB_1_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 415ce75..50221bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -22,32 +22,42 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class MobileIconViewModelTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconViewModel
-    private val interactor = FakeMobileIconInteractor()
+    private lateinit var interactor: FakeMobileIconInteractor
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var constants: ConnectivityConstants
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        interactor = FakeMobileIconInteractor(tableLogBuffer)
         interactor.apply {
             setLevel(1)
             setIsDefaultDataEnabled(true)
@@ -57,12 +67,13 @@
             setNumberOfLevels(4)
             isDataConnected.value = true
         }
-        underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+        underTest =
+            MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
     }
 
     @Test
     fun iconId_correctLevel_notCutout() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.iconId.onEach { latest = it }.launchIn(this)
             val expected = defaultSignal()
@@ -74,7 +85,7 @@
 
     @Test
     fun iconId_cutout_whenDefaultDataDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIsDefaultDataEnabled(false)
 
             var latest: Int? = null
@@ -88,7 +99,7 @@
 
     @Test
     fun networkType_dataEnabled_groupIsRepresented() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
@@ -106,7 +117,7 @@
 
     @Test
     fun networkType_nullWhenDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsDataEnabled(false)
             var latest: Icon? = null
@@ -119,7 +130,7 @@
 
     @Test
     fun networkType_nullWhenFailedConnection() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsDataEnabled(true)
             interactor.setIsFailedConnection(true)
@@ -133,7 +144,7 @@
 
     @Test
     fun networkType_nullWhenDataDisconnects() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val initial =
                 Icon.Resource(
                     THREE_G.dataType,
@@ -157,7 +168,7 @@
 
     @Test
     fun networkType_null_changeToDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
@@ -180,7 +191,7 @@
 
     @Test
     fun networkType_alwaysShow_shownEvenWhenDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsDataEnabled(true)
             interactor.alwaysShowDataRatIcon.value = true
@@ -200,7 +211,7 @@
 
     @Test
     fun networkType_alwaysShow_shownEvenWhenDisconnected() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.isDataConnected.value = false
             interactor.alwaysShowDataRatIcon.value = true
@@ -220,7 +231,7 @@
 
     @Test
     fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsFailedConnection(true)
             interactor.alwaysShowDataRatIcon.value = true
@@ -240,7 +251,7 @@
 
     @Test
     fun roaming() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.isRoaming.value = true
             var latest: Boolean? = null
             val job = underTest.roaming.onEach { latest = it }.launchIn(this)
@@ -256,10 +267,17 @@
 
     @Test
     fun `data activity - null when config is off`() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             // Create a new view model here so the constants are properly read
             whenever(constants.shouldShowActivityConfig).thenReturn(false)
-            underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+            underTest =
+                MobileIconViewModel(
+                    SUB_1_ID,
+                    interactor,
+                    logger,
+                    constants,
+                    testScope.backgroundScope,
+                )
 
             var inVisible: Boolean? = null
             val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -288,10 +306,17 @@
 
     @Test
     fun `data activity - config on - test indicators`() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             // Create a new view model here so the constants are properly read
             whenever(constants.shouldShowActivityConfig).thenReturn(true)
-            underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+            underTest =
+                MobileIconViewModel(
+                    SUB_1_ID,
+                    interactor,
+                    logger,
+                    constants,
+                    testScope.backgroundScope,
+                )
 
             var inVisible: Boolean? = null
             val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -340,16 +365,15 @@
             containerJob.cancel()
         }
 
-    /** Convenience constructor for these tests */
-    private fun defaultSignal(
-        level: Int = 1,
-        connected: Boolean = true,
-    ): Int {
-        return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
-    }
-
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
+
+        /** Convenience constructor for these tests */
+        fun defaultSignal(
+            level: Int = 1,
+            connected: Boolean = true,
+        ): Int {
+            return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
new file mode 100644
index 0000000..d6cb762
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileIconsViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconsViewModel
+    private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var constants: ConnectivityConstants
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val subscriptionIdsFlow =
+            interactor.filteredSubscriptions
+                .map { subs -> subs.map { it.subscriptionId } }
+                .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
+
+        underTest =
+            MobileIconsViewModel(
+                subscriptionIdsFlow,
+                interactor,
+                logger,
+                constants,
+                testScope.backgroundScope,
+            )
+
+        interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+    }
+
+    @Test
+    fun `caching - mobile icon view model is reused for same sub id`() =
+        testScope.runTest {
+            val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+            val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS)
+
+            assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl)
+        }
+
+    @Test
+    fun `caching - invalid view models are removed from cache when sub disappears`() =
+        testScope.runTest {
+            // Retrieve models to trigger caching
+            val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+            val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+
+            // Both impls are cached
+            assertThat(underTest.mobileIconSubIdCache)
+                .containsExactly(1, model1.commonImpl, 2, model2.commonImpl)
+
+            // SUB_1 is removed from the list...
+            interactor.filteredSubscriptions.value = listOf(SUB_2)
+
+            // ... and dropped from the cache
+            assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
+        }
+
+    companion object {
+        private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false)
+        private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
new file mode 100644
index 0000000..b935442
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 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.pipeline.wifi.data.repository
+
+import android.net.ConnectivityManager
+import android.net.wifi.WifiManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiRepositorySwitcherTest : SysuiTestCase() {
+    private lateinit var underTest: WifiRepositorySwitcher
+    private lateinit var realImpl: WifiRepositoryImpl
+    private lateinit var demoImpl: DemoWifiRepository
+
+    @Mock private lateinit var demoModeController: DemoModeController
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
+    @Mock private lateinit var connectivityManager: ConnectivityManager
+    @Mock private lateinit var wifiManager: WifiManager
+    @Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+    private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
+
+    private val mainExecutor = FakeExecutor(FakeSystemClock())
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        // Never start in demo mode
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+        realImpl =
+            WifiRepositoryImpl(
+                fakeBroadcastDispatcher,
+                connectivityManager,
+                logger,
+                tableLogger,
+                mainExecutor,
+                testScope.backgroundScope,
+                wifiManager,
+            )
+
+        whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
+
+        demoImpl =
+            DemoWifiRepository(
+                demoModeWifiDataSource,
+                testScope.backgroundScope,
+            )
+
+        underTest =
+            WifiRepositorySwitcher(
+                realImpl,
+                demoImpl,
+                demoModeController,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun `switcher active repo - updates when demo mode changes`() =
+        testScope.runTest {
+            assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl)
+
+            var latest: WifiRepository? = null
+            val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+            startDemoMode()
+
+            assertThat(latest).isSameInstanceAs(demoImpl)
+
+            finishDemoMode()
+
+            assertThat(latest).isSameInstanceAs(realImpl)
+
+            job.cancel()
+        }
+
+    private fun startDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(true)
+        getDemoModeCallback().onDemoModeStarted()
+    }
+
+    private fun finishDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+        getDemoModeCallback().onDemoModeFinished()
+    }
+
+    private fun getDemoModeCallback(): DemoMode {
+        val captor = kotlinArgumentCaptor<DemoMode>()
+        Mockito.verify(demoModeController).addCallback(captor.capture())
+        return captor.value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index b47f177..4158434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -146,7 +146,7 @@
 
     @Test
     fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -183,7 +183,7 @@
 
     @Test
     fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -225,7 +225,7 @@
 
     @Test
     fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
 
         wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
@@ -268,7 +268,7 @@
 
     @Test
     fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -308,7 +308,7 @@
 
     @Test
     fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -330,7 +330,7 @@
 
     @Test
     fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -352,7 +352,7 @@
 
     @Test
     fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -374,7 +374,7 @@
 
     @Test
     fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -396,7 +396,7 @@
 
     @Test
     fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -418,7 +418,7 @@
 
     @Test
     fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -440,7 +440,7 @@
 
     @Test
     fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -462,7 +462,7 @@
 
     @Test
     fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 2c47204..4b32ee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -55,6 +55,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.EditText;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
@@ -414,7 +415,9 @@
                 mDependency,
                 TestableLooper.get(this));
         ExpandableNotificationRow row = helper.createRow();
+        FrameLayout remoteInputViewParent = new FrameLayout(mContext);
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        remoteInputViewParent.addView(view);
         bindController(view, row.getEntry());
 
         // Start defocus animation
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5501949..39d2eca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -46,12 +46,18 @@
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
+    private val _isKeyguardOccluded = MutableStateFlow(false)
+    override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
+
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: Flow<Boolean> = _isDozing
 
     private val _isDreaming = MutableStateFlow(false)
     override val isDreaming: Flow<Boolean> = _isDreaming
 
+    private val _isDreamingWithOverlay = MutableStateFlow(false)
+    override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
+
     private val _dozeAmount = MutableStateFlow(0f)
     override val linearDozeAmount: Flow<Float> = _dozeAmount
 
@@ -112,10 +118,18 @@
         _isKeyguardShowing.value = isShowing
     }
 
+    fun setKeyguardOccluded(isOccluded: Boolean) {
+        _isKeyguardOccluded.value = isOccluded
+    }
+
     fun setDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
     }
 
+    fun setDreamingWithOverlay(isDreaming: Boolean) {
+        _isDreamingWithOverlay.value = isDreaming
+    }
+
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
@@ -144,6 +158,10 @@
         _fingerprintSensorLocation.tryEmit(location)
     }
 
+    fun setDozeTransitionModel(model: DozeTransitionModel) {
+        _dozeTransitionModel.value = model
+    }
+
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
diff --git a/services/api/current.txt b/services/api/current.txt
index b5798d5..090a449 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -72,16 +72,90 @@
 package com.android.server.pm.pkg {
 
   public interface AndroidPackage {
+    method @Nullable public String getAppComponentFactory();
+    method @Nullable public String getApplicationClassName();
+    method @Nullable public String getBackupAgentName();
+    method @DrawableRes public int getBannerRes();
+    method public int getBaseRevisionCode();
+    method public int getCategory();
+    method @Nullable public String getClassLoaderName();
+    method @Dimension(unit=android.annotation.Dimension.DP) public int getCompatibleWidthLimitDp();
+    method @XmlRes public int getDataExtractionRulesRes();
+    method @StringRes public int getDescriptionRes();
+    method @XmlRes public int getFullBackupContentRes();
+    method public int getGwpAsanMode();
+    method @DrawableRes public int getIconRes();
+    method @StringRes public int getLabelRes();
+    method @Dimension(unit=android.annotation.Dimension.DP) public int getLargestWidthLimitDp();
     method @NonNull public java.util.List<java.lang.String> getLibraryNames();
+    method @XmlRes public int getLocaleConfigRes();
+    method @DrawableRes public int getLogoRes();
+    method public long getLongVersionCode();
+    method public float getMaxAspectRatio();
+    method public float getMinAspectRatio();
+    method public int getNativeHeapZeroInitialized();
+    method @XmlRes public int getNetworkSecurityConfigRes();
+    method @Nullable public String getRequiredAccountType();
+    method @Dimension(unit=android.annotation.Dimension.DP) public int getRequiresSmallestWidthDp();
+    method @Nullable public String getRestrictedAccountType();
+    method @DrawableRes public int getRoundIconRes();
     method @Nullable public String getSdkLibraryName();
+    method @Nullable public String getSharedUserId();
+    method @StringRes public int getSharedUserLabelRes();
     method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
     method @Nullable public String getStaticSharedLibraryName();
     method @NonNull public java.util.UUID getStorageUuid();
     method public int getTargetSdkVersion();
+    method @StyleRes public int getThemeRes();
+    method public int getUiOptions();
+    method @Nullable public String getVersionName();
+    method @Nullable public String getZygotePreloadName();
+    method public boolean isAllowAudioPlaybackCapture();
+    method public boolean isAllowBackup();
+    method public boolean isAllowClearUserData();
+    method public boolean isAllowClearUserDataOnFailedRestore();
+    method public boolean isAllowNativeHeapPointerTagging();
+    method public boolean isAllowTaskReparenting();
+    method public boolean isAnyDensity();
+    method public boolean isAttributionsUserVisible();
+    method public boolean isBackupInForeground();
+    method public boolean isCantSaveState();
+    method public boolean isCoreApp();
+    method public boolean isCrossProfile();
     method public boolean isDebuggable();
+    method public boolean isDefaultToDeviceProtectedStorage();
+    method public boolean isDirectBootAware();
+    method public boolean isExtractNativeLibs();
+    method public boolean isFactoryTest();
+    method public boolean isForceQueryable();
+    method public boolean isFullBackupOnly();
+    method public boolean isHardwareAccelerated();
+    method public boolean isHasCode();
+    method public boolean isHasFragileUserData();
     method public boolean isIsolatedSplitLoading();
+    method public boolean isKillAfterRestore();
+    method public boolean isLargeHeap();
+    method public boolean isLeavingSharedUser();
+    method public boolean isMultiArch();
+    method public boolean isNativeLibraryRootRequiresIsa();
+    method public boolean isOnBackInvokedCallbackEnabled();
+    method public boolean isPersistent();
+    method public boolean isProfileable();
+    method public boolean isProfileableByShell();
+    method public boolean isRequestLegacyExternalStorage();
+    method public boolean isRequiredForAllUsers();
+    method public boolean isResetEnabledSettingsOnAppDataCleared();
+    method public boolean isRestoreAnyVersion();
     method public boolean isSignedWithPlatformKey();
+    method public boolean isSupportsExtraLargeScreens();
+    method public boolean isSupportsLargeScreens();
+    method public boolean isSupportsNormalScreens();
+    method public boolean isSupportsRtl();
+    method public boolean isSupportsSmallScreens();
+    method public boolean isTestOnly();
+    method public boolean isUse32BitAbi();
     method public boolean isUseEmbeddedDex();
+    method public boolean isUsesCleartextTraffic();
     method public boolean isUsesNonSdkApi();
     method public boolean isVmSafeMode();
   }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1c571a7..53f5fe1 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,6 +25,8 @@
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -1556,6 +1558,22 @@
         }
     }
 
+    public void reportDelayedRestoreResult(String packageName, List<DataTypeResult> results) {
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!isUserReadyForBackup(userId)) {
+            Slog.w(TAG, "Returning from reportDelayedRestoreResult as backup for user" + userId +
+                    " is not initialized yet");
+            return;
+        }
+        UserBackupManagerService userBackupManagerService =
+                getServiceForUserIfCallerHasPermission(userId,
+                        /* caller */ "reportDelayedRestoreResult()");
+
+        if (userBackupManagerService != null) {
+            userBackupManagerService.reportDelayedRestoreResult(packageName, results);
+        }
+    }
+
     /**
      * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
      * If the user is not registered with the service (either the user is locked or not eligible for
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ce3e628..6ba01d7 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -49,6 +49,7 @@
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -505,13 +506,14 @@
 
     @VisibleForTesting
     UserBackupManagerService(Context context, PackageManager packageManager,
-            LifecycleOperationStorage operationStorage) {
+            LifecycleOperationStorage operationStorage, TransportManager transportManager) {
         mContext = context;
 
         mUserId = 0;
         mRegisterTransportsRequestedTime = 0;
         mPackageManager = packageManager;
         mOperationStorage = operationStorage;
+        mTransportManager = transportManager;
 
         mBaseStateDir = null;
         mDataDir = null;
@@ -521,7 +523,6 @@
         mRunInitReceiver = null;
         mRunInitIntent = null;
         mAgentTimeoutParameters = null;
-        mTransportManager = null;
         mActivityManagerInternal = null;
         mAlarmManager = null;
         mConstants = null;
@@ -3038,6 +3039,37 @@
         mBackupPreferences.addExcludedKeys(packageName, keys);
     }
 
+    public void reportDelayedRestoreResult(String packageName,
+            List<BackupRestoreEventLogger.DataTypeResult> results) {
+        String transport = mTransportManager.getCurrentTransportName();
+        if (transport == null) {
+            Slog.w(TAG, "Failed to send delayed restore logs as no transport selected");
+            return;
+        }
+
+        TransportConnection transportConnection = null;
+        try {
+            PackageInfo packageInfo = getPackageManager().getPackageInfoAsUser(packageName,
+                    PackageManager.PackageInfoFlags.of(/* value */ 0), getUserId());
+
+            transportConnection = mTransportManager.getTransportClientOrThrow(
+                    transport, /* caller */"BMS.reportDelayedRestoreResult");
+            BackupTransportClient transportClient = transportConnection.connectOrThrow(
+                    /* caller */ "BMS.reportDelayedRestoreResult");
+
+            IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor();
+            BackupManagerMonitorUtils.sendAgentLoggingResults(monitor, packageInfo, results);
+        } catch (NameNotFoundException | TransportNotAvailableException
+                | TransportNotRegisteredException | RemoteException e) {
+            Slog.w(TAG, "Failed to send delayed restore logs: " + e);
+        } finally {
+            if (transportConnection != null) {
+                mTransportManager.disposeOfTransportClient(transportConnection,
+                        /* caller */"BMS.reportDelayedRestoreResult");
+            }
+        }
+    }
+
     private boolean startConfirmationUi(int token, String action) {
         try {
             Intent confIntent = new Intent(action);
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
index 8eda5b9..57ad89b 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
@@ -24,10 +24,11 @@
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.backup.BackupManagerMonitor;
-import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManagerMonitor;
 import android.content.pm.PackageInfo;
 import android.os.Bundle;
@@ -119,19 +120,11 @@
         }
 
         try {
-            AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture =
+            AndroidFuture<List<DataTypeResult>> resultsFuture =
                     new AndroidFuture<>();
             agent.getLoggerResults(resultsFuture);
-            Bundle loggerResultsBundle = new Bundle();
-            loggerResultsBundle.putParcelableList(
-                    EXTRA_LOG_AGENT_LOGGING_RESULTS,
+            return sendAgentLoggingResults(monitor, pkg,
                     resultsFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-            return BackupManagerMonitorUtils.monitorEvent(
-                    monitor,
-                    LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
-                    pkg,
-                    LOG_EVENT_CATEGORY_AGENT,
-                    loggerResultsBundle);
         } catch (TimeoutException e) {
             Slog.w(TAG, "Timeout while waiting to retrieve logging results from agent", e);
         } catch (Exception e) {
@@ -140,6 +133,19 @@
         return monitor;
     }
 
+    public static IBackupManagerMonitor sendAgentLoggingResults(
+            @NonNull IBackupManagerMonitor monitor, PackageInfo pkg, List<DataTypeResult> results) {
+        Bundle loggerResultsBundle = new Bundle();
+        loggerResultsBundle.putParcelableList(
+                EXTRA_LOG_AGENT_LOGGING_RESULTS, results);
+        return monitorEvent(
+                monitor,
+                LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
+                pkg,
+                LOG_EVENT_CATEGORY_AGENT,
+                loggerResultsBundle);
+    }
+
     /**
      * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
      * be created.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 4d173d6..cdd5471 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -22,9 +22,9 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -62,6 +62,7 @@
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.Looper;
+import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -198,6 +199,7 @@
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params) {
+        super(PermissionEnforcer.fromContext(context));
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
@@ -337,11 +339,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to close the virtual device");
-
+        super.close_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mPerDisplayWakelocks.isEmpty()) {
                 mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
@@ -389,14 +389,12 @@
         return mWindowPolicyControllers;
     }
 
-    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionStarting(int displayId,
             @NonNull IAudioRoutingCallback routingCallback,
             @Nullable IAudioConfigChangedCallback configChangedCallback) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to start audio session");
+        super.onAudioSessionStarting_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(displayId)) {
                 throw new SecurityException(
@@ -413,12 +411,10 @@
         }
     }
 
-    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionEnded() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to stop audio session");
+        super.onAudioSessionEnded_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (mVirtualAudioController != null) {
                 mVirtualAudioController.stopListening();
@@ -428,9 +424,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual dpad");
+        super.createVirtualDpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -448,9 +444,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual keyboard");
+        super.createVirtualKeyboard_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -470,9 +466,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual mouse");
+        super.createVirtualMouse_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -490,10 +486,10 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
             @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual touchscreen");
+        super.createVirtualTouchscreen_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -501,30 +497,29 @@
                                 + "this virtual device");
             }
         }
-        int screenHeightPixels = config.getHeightInPixels();
-        int screenWidthPixels = config.getWidthInPixels();
-        if (screenHeightPixels <= 0 || screenWidthPixels <= 0) {
+        int screenHeight = config.getHeight();
+        int screenWidth = config.getWidth();
+        if (screenHeight <= 0 || screenWidth <= 0) {
             throw new IllegalArgumentException(
                     "Cannot create a virtual touchscreen, screen dimensions must be positive. Got: "
-                            + "(" + screenWidthPixels + ", " + screenHeightPixels + ")");
+                            + "(" + screenWidth + ", " + screenHeight + ")");
         }
 
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),
                     config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
-                    screenHeightPixels, screenWidthPixels);
+                    screenHeight, screenWidth);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
             @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual navigation touchpad");
+        super.createVirtualNavigationTouchpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -552,11 +547,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterInputDevice(IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister this input device");
-
+        super.unregisterInputDevice_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.unregisterInputDevice(token);
@@ -577,7 +570,9 @@
 
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
+        super.sendDpadKeyEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendDpadKeyEvent(token, event);
@@ -587,7 +582,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
+        super.sendKeyEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendKeyEvent(token, event);
@@ -597,7 +594,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
+        super.sendButtonEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendButtonEvent(token, event);
@@ -607,7 +606,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
+        super.sendTouchEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendTouchEvent(token, event);
@@ -617,7 +618,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
+        super.sendRelativeEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendRelativeEvent(token, event);
@@ -627,7 +630,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
+        super.sendScrollEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendScrollEvent(token, event);
@@ -647,11 +652,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void setShowPointerIcon(boolean showPointerIcon) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister this input device");
-
+        super.setShowPointerIcon_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mVirtualDeviceLock) {
@@ -666,12 +669,11 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualSensor(
             @NonNull IBinder deviceToken,
             @NonNull VirtualSensorConfig config) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual sensor");
+        super.createVirtualSensor_enforcePermission();
         Objects.requireNonNull(config);
         Objects.requireNonNull(deviceToken);
         final long ident = Binder.clearCallingIdentity();
@@ -683,10 +685,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterSensor(@NonNull IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister a virtual sensor");
+        super.unregisterSensor_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             mSensorController.unregisterSensor(token);
@@ -696,10 +697,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to send a virtual sensor event");
+        super.sendSensorEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mSensorController.sendSensorEvent(token, event);
@@ -709,25 +709,23 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor,
             IntentFilter filter) {
+        super.registerIntentInterceptor_enforcePermission();
         Objects.requireNonNull(intentInterceptor);
         Objects.requireNonNull(filter);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to register intent interceptor");
         synchronized (mVirtualDeviceLock) {
             mIntentInterceptors.put(intentInterceptor.asBinder(), filter);
         }
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterIntentInterceptor(
             @NonNull IVirtualDeviceIntentInterceptor intentInterceptor) {
+        super.unregisterIntentInterceptor_enforcePermission();
         Objects.requireNonNull(intentInterceptor);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister intent interceptor");
         synchronized (mVirtualDeviceLock) {
             mIntentInterceptors.remove(intentInterceptor.asBinder());
         }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9bedbd0..7b8ca91 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1000,6 +1000,10 @@
     @Override
     public void notifySubscriptionInfoChanged() {
         if (VDBG) log("notifySubscriptionInfoChanged:");
+        if (!checkNotifyPermission("notifySubscriptionInfoChanged()")) {
+            return;
+        }
+
         synchronized (mRecords) {
             if (!mHasNotifySubscriptionInfoChangedOccurred) {
                 log("notifySubscriptionInfoChanged: first invocation mRecords.size="
@@ -1026,6 +1030,10 @@
     @Override
     public void notifyOpportunisticSubscriptionInfoChanged() {
         if (VDBG) log("notifyOpptSubscriptionInfoChanged:");
+        if (!checkNotifyPermission("notifyOpportunisticSubscriptionInfoChanged()")) {
+            return;
+        }
+
         synchronized (mRecords) {
             if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
                 log("notifyOpptSubscriptionInfoChanged: first invocation mRecords.size="
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 45b11e1..e06ce2c9 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -2314,7 +2314,8 @@
 
     void printCurrentCpuState(StringBuilder report, long time) {
         synchronized (mProcessCpuTracker) {
-            report.append(mProcessCpuTracker.printCurrentState(time));
+            // Only print the first 10 processes
+            report.append(mProcessCpuTracker.printCurrentState(time, /* maxProcesses= */10));
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d53f8fb..b89084c 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -42,6 +42,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -124,6 +125,12 @@
     private final ArrayDeque<SomeArgs> mPendingOffload = new ArrayDeque<>(4);
 
     /**
+     * List of all queues holding broadcasts that are waiting to be dispatched.
+     */
+    private final List<ArrayDeque<SomeArgs>> mPendingQueues = List.of(
+            mPendingUrgent, mPending, mPendingOffload);
+
+    /**
      * Broadcast actively being dispatched to this process.
      */
     private @Nullable BroadcastRecord mActive;
@@ -218,11 +225,11 @@
      * given count of other receivers have reached a terminal state; typically
      * used for ordered broadcasts and priority traunches.
      */
-    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
+    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
+            @NonNull BroadcastConsumer replacedBroadcastConsumer) {
         if (record.isReplacePending()) {
-            boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex)
-                    || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex)
-                    || replaceBroadcastInQueue(mPendingOffload, record, recordIndex);
+            final boolean didReplace = replaceBroadcast(record, recordIndex,
+                    replacedBroadcastConsumer);
             if (didReplace) {
                 return;
             }
@@ -243,6 +250,26 @@
     }
 
     /**
+     * Searches from newest to oldest in the pending broadcast queues, and at the first matching
+     * pending broadcast it finds, replaces it in-place and returns -- does not attempt to handle
+     * "duplicate" broadcasts in the queue.
+     * <p>
+     * @return {@code true} if it found and replaced an existing record in the queue;
+     * {@code false} otherwise.
+     */
+    private boolean replaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
+            @NonNull BroadcastConsumer replacedBroadcastConsumer) {
+        final int count = mPendingQueues.size();
+        for (int i = 0; i < count; ++i) {
+            final ArrayDeque<SomeArgs> queue = mPendingQueues.get(i);
+            if (replaceBroadcastInQueue(queue, record, recordIndex, replacedBroadcastConsumer)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Searches from newest to oldest, and at the first matching pending broadcast
      * it finds, replaces it in-place and returns -- does not attempt to handle
      * "duplicate" broadcasts in the queue.
@@ -251,7 +278,8 @@
      * {@code false} otherwise.
      */
     private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
-            @NonNull BroadcastRecord record, int recordIndex) {
+            @NonNull BroadcastRecord record, int recordIndex,
+            @NonNull BroadcastConsumer replacedBroadcastConsumer) {
         final Iterator<SomeArgs> it = queue.descendingIterator();
         final Object receiver = record.receivers.get(recordIndex);
         while (it.hasNext()) {
@@ -262,12 +290,14 @@
             if ((record.callingUid == testRecord.callingUid)
                     && (record.userId == testRecord.userId)
                     && record.intent.filterEquals(testRecord.intent)
-                    && isReceiverEquals(receiver, testReceiver)) {
+                    && isReceiverEquals(receiver, testReceiver)
+                    && testRecord.allReceiversPending()) {
                 // Exact match found; perform in-place swap
                 args.arg1 = record;
                 args.argi1 = recordIndex;
                 onBroadcastDequeued(testRecord, testRecordIndex);
                 onBroadcastEnqueued(record, recordIndex);
+                replacedBroadcastConsumer.accept(testRecord, testRecordIndex);
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a850c8a..8f241b2 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -64,6 +64,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.MathUtils;
 import android.util.Pair;
@@ -629,30 +630,26 @@
 
         applyDeliveryGroupPolicy(r);
 
-        if (r.isReplacePending()) {
-            // Leave the skipped broadcasts intact in queue, so that we can
-            // replace them at their current position during enqueue below
-            forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
-                // We only allow caller to replace broadcasts they enqueued
-                return (r.callingUid == testRecord.callingUid)
-                        && (r.userId == testRecord.userId)
-                        && r.intent.filterEquals(testRecord.intent);
-            }, mBroadcastConsumerSkipAndCanceled, false);
-        }
-
         r.enqueueTime = SystemClock.uptimeMillis();
         r.enqueueRealTime = SystemClock.elapsedRealtime();
         r.enqueueClockTime = System.currentTimeMillis();
 
+        final ArraySet<BroadcastRecord> replacedBroadcasts = new ArraySet<>();
+        final BroadcastConsumer replacedBroadcastConsumer =
+                (record, i) -> replacedBroadcasts.add(record);
         for (int i = 0; i < r.receivers.size(); i++) {
             final Object receiver = r.receivers.get(i);
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     getReceiverProcessName(receiver), getReceiverUid(receiver));
-            queue.enqueueOrReplaceBroadcast(r, i);
+            queue.enqueueOrReplaceBroadcast(r, i, replacedBroadcastConsumer);
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
 
+        // Skip any broadcasts that have been replaced by newer broadcasts with
+        // FLAG_RECEIVER_REPLACE_PENDING.
+        skipAndCancelReplacedBroadcasts(replacedBroadcasts);
+
         // If nothing to dispatch, send any pending result immediately
         if (r.receivers.isEmpty()) {
             scheduleResultTo(r);
@@ -662,6 +659,17 @@
         traceEnd(cookie);
     }
 
+    private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) {
+        for (int i = 0; i < replacedBroadcasts.size(); ++i) {
+            final BroadcastRecord r = replacedBroadcasts.valueAt(i);
+            r.resultCode = Activity.RESULT_CANCELED;
+            r.resultData = null;
+            r.resultExtras = null;
+            scheduleResultTo(r);
+            notifyFinishBroadcast(r);
+        }
+    }
+
     private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
         if (mService.shouldIgnoreDeliveryGroupPolicy(r.intent.getAction())) {
             return;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 24cf3d2..37225d1 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -907,6 +907,17 @@
         return record.options == null ? null : record.options.getDeliveryGroupMatchingFilter();
     }
 
+    /**
+     * Returns {@code true} if all the receivers are still waiting to receive the broadcast.
+     * Otherwise {@code false}.
+     */
+    boolean allReceiversPending() {
+        // We could also count the number of receivers with deliver state DELIVERY_PENDING, but
+        // checking how many receivers have finished (either skipped or cancelled) and whether or
+        // not the dispatch has been started should be sufficient.
+        return (terminalCount == 0 && dispatchTime <= 0);
+    }
+
     @Override
     public String toString() {
         if (mCachedToString == null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 418027f..9877ed3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -36,6 +36,7 @@
 import android.media.IAudioRoutesObserver;
 import android.media.ICapturePresetDevicesRoleDispatcher;
 import android.media.ICommunicationDeviceDispatcher;
+import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -871,6 +872,16 @@
         return mDeviceInventory.removePreferredDevicesForStrategySync(strategy);
     }
 
+    /*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy,
+            @NonNull AudioDeviceAttributes device) {
+        return mDeviceInventory.setDeviceAsNonDefaultForStrategySync(strategy, device);
+    }
+
+    /*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy,
+            @NonNull AudioDeviceAttributes device) {
+        return mDeviceInventory.removeDeviceAsNonDefaultForStrategySync(strategy, device);
+    }
+
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
             @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
         mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
@@ -881,6 +892,16 @@
         mDeviceInventory.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
+    /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
+        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+    }
+
+    /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
+        mDeviceInventory.unregisterStrategyNonDefaultDevicesDispatcher(dispatcher);
+    }
+
     /*package*/ int setPreferredDevicesForCapturePresetSync(int capturePreset,
             @NonNull List<AudioDeviceAttributes> devices) {
         return mDeviceInventory.setPreferredDevicesForCapturePresetSync(capturePreset, devices);
@@ -1039,6 +1060,17 @@
         sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
     }
 
+    /*package*/ void postSaveSetDeviceAsNonDefaultForStrategy(
+            int strategy, AudioDeviceAttributes device) {
+        sendILMsgNoDelay(MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
+    }
+
+    /*package*/ void postSaveRemoveDeviceAsNonDefaultForStrategy(
+            int strategy, AudioDeviceAttributes device) {
+        sendILMsgNoDelay(
+                MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
+    }
+
     /*package*/ void postSaveSetPreferredDevicesForCapturePreset(
             int capturePreset, List<AudioDeviceAttributes> devices) {
         sendILMsgNoDelay(
@@ -1508,6 +1540,16 @@
                     final int strategy = msg.arg1;
                     mDeviceInventory.onSaveRemovePreferredDevices(strategy);
                 } break;
+                case MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY: {
+                    final int strategy = msg.arg1;
+                    final AudioDeviceAttributes device = (AudioDeviceAttributes) msg.obj;
+                    mDeviceInventory.onSaveSetDeviceAsNonDefault(strategy, device);
+                } break;
+                case MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY: {
+                    final int strategy = msg.arg1;
+                    final AudioDeviceAttributes device = (AudioDeviceAttributes) msg.obj;
+                    mDeviceInventory.onSaveRemoveDeviceAsNonDefault(strategy, device);
+                } break;
                 case MSG_CHECK_MUTE_MUSIC:
                     checkMessagesMuteMusic(0);
                     break;
@@ -1593,6 +1635,9 @@
     // process set volume for Le Audio, obj is BleVolumeInfo
     private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46;
 
+    private static final int MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY = 47;
+    private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48;
+
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 34457b0..f9270c9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -31,8 +31,11 @@
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
 import android.media.ICapturePresetDevicesRoleDispatcher;
+import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.SafeCloseable;
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -142,6 +145,10 @@
     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices =
             new ArrayMap<>();
 
+    // List of non-default devices for strategies
+    private final ArrayMap<Integer, List<AudioDeviceAttributes>> mNonDefaultDevices =
+            new ArrayMap<>();
+
     // List of preferred devices of capture preset
     private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset =
             new ArrayMap<>();
@@ -156,10 +163,14 @@
     final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
             new RemoteCallbackList<IAudioRoutesObserver>();
 
-    // Monitoring of strategy-preferred device
+    // Monitoring of preferred device for strategies
     final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers =
             new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>();
 
+    // Monitoring of non-default device for strategies
+    final RemoteCallbackList<IStrategyNonDefaultDevicesDispatcher> mNonDefDevDispatchers =
+            new RemoteCallbackList<IStrategyNonDefaultDevicesDispatcher>();
+
     // Monitoring of devices for role and capture preset
     final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
             new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
@@ -254,6 +265,9 @@
         pw.println("\n" + prefix + "Preferred devices for strategy:");
         mPreferredDevices.forEach((strategy, device) -> {
             pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
+        pw.println("\n" + prefix + "Non-default devices for strategy:");
+        mNonDefaultDevices.forEach((strategy, device) -> {
+            pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
         pw.println("\n" + prefix + "Connected devices:");
         mConnectedDevices.forEach((key, deviceInfo) -> {
             pw.println("  " + prefix + deviceInfo.toString()); });
@@ -291,6 +305,11 @@
                 mAudioSystem.setDevicesRoleForStrategy(
                         strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
         }
+        synchronized (mNonDefaultDevices) {
+            mNonDefaultDevices.forEach((strategy, devices) -> {
+                mAudioSystem.setDevicesRoleForStrategy(
+                        strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); });
+        }
         synchronized (mPreferredDevicesForCapturePreset) {
             // TODO: call audiosystem to restore
         }
@@ -608,6 +627,18 @@
     /*package*/ void onSaveSetPreferredDevices(int strategy,
                                                @NonNull List<AudioDeviceAttributes> devices) {
         mPreferredDevices.put(strategy, devices);
+        List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy);
+        if (nonDefaultDevices != null) {
+            nonDefaultDevices.removeAll(devices);
+
+            if (nonDefaultDevices.isEmpty()) {
+                mNonDefaultDevices.remove(strategy);
+            } else {
+                mNonDefaultDevices.put(strategy, nonDefaultDevices);
+            }
+            dispatchNonDefaultDevice(strategy, nonDefaultDevices);
+        }
+
         dispatchPreferredDevice(strategy, devices);
     }
 
@@ -616,6 +647,40 @@
         dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>());
     }
 
+    /*package*/ void onSaveSetDeviceAsNonDefault(int strategy,
+                                                 @NonNull AudioDeviceAttributes device) {
+        List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy);
+        if (nonDefaultDevices == null) {
+            nonDefaultDevices = new ArrayList<>();
+        }
+
+        if (!nonDefaultDevices.contains(device)) {
+            nonDefaultDevices.add(device);
+        }
+
+        mNonDefaultDevices.put(strategy, nonDefaultDevices);
+        dispatchNonDefaultDevice(strategy, nonDefaultDevices);
+
+        List<AudioDeviceAttributes> preferredDevices = mPreferredDevices.get(strategy);
+
+        if (preferredDevices != null) {
+            preferredDevices.remove(device);
+            mPreferredDevices.put(strategy, preferredDevices);
+
+            dispatchPreferredDevice(strategy, preferredDevices);
+        }
+    }
+
+    /*package*/ void onSaveRemoveDeviceAsNonDefault(int strategy,
+                                                    @NonNull AudioDeviceAttributes device) {
+        List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy);
+        if (nonDefaultDevices != null) {
+            nonDefaultDevices.remove(device);
+            mNonDefaultDevices.put(strategy, nonDefaultDevices);
+            dispatchNonDefaultDevice(strategy, nonDefaultDevices);
+        }
+    }
+
     /*package*/ void onSaveSetPreferredDevicesForCapturePreset(
             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
         mPreferredDevicesForCapturePreset.put(capturePreset, devices);
@@ -631,18 +696,19 @@
     }
 
     //------------------------------------------------------------
-    // preferred device(s)
+    // preferred/non-default device(s)
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
             @NonNull List<AudioDeviceAttributes> devices) {
-        final long identity = Binder.clearCallingIdentity();
+        int status = AudioSystem.ERROR;
 
-        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                                "setPreferredDevicesForStrategySync, strategy: " + strategy
-                                + " devices: " + devices)).printLog(TAG));
-        final int status = mAudioSystem.setDevicesRoleForStrategy(
-                strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
-        Binder.restoreCallingIdentity(identity);
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                            "setPreferredDevicesForStrategySync, strategy: " + strategy
+                            + " devices: " + devices)).printLog(TAG));
+            status = mAudioSystem.setDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+        }
 
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
@@ -651,15 +717,16 @@
     }
 
     /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
-        final long identity = Binder.clearCallingIdentity();
+        int status = AudioSystem.ERROR;
 
-        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                "removePreferredDevicesForStrategySync, strategy: "
-                + strategy)).printLog(TAG));
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                            "removePreferredDevicesForStrategySync, strategy: "
+                            + strategy)).printLog(TAG));
 
-        final int status = mAudioSystem.removeDevicesRoleForStrategy(
-                strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
-        Binder.restoreCallingIdentity(identity);
+            status = mAudioSystem.clearDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+        }
 
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
@@ -667,6 +734,50 @@
         return status;
     }
 
+    /*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy,
+            @NonNull AudioDeviceAttributes device) {
+        int status = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            List<AudioDeviceAttributes> devices = new ArrayList<>();
+            devices.add(device);
+
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                            "setDeviceAsNonDefaultForStrategySync, strategy: " + strategy
+                            + " device: " + device)).printLog(TAG));
+            status = mAudioSystem.setDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+        }
+
+        if (status == AudioSystem.SUCCESS) {
+            mDeviceBroker.postSaveSetDeviceAsNonDefaultForStrategy(strategy, device);
+        }
+        return status;
+    }
+
+    /*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy,
+            @NonNull AudioDeviceAttributes device) {
+        int status = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            List<AudioDeviceAttributes> devices = new ArrayList<>();
+            devices.add(device);
+
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                            "removeDeviceAsNonDefaultForStrategySync, strategy: "
+                            + strategy + " devices: " + device)).printLog(TAG));
+
+            status = mAudioSystem.removeDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+        }
+
+        if (status == AudioSystem.SUCCESS) {
+            mDeviceBroker.postSaveRemoveDeviceAsNonDefaultForStrategy(strategy, device);
+        }
+        return status;
+    }
+
+
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
             @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
         mPrefDevDispatchers.register(dispatcher);
@@ -677,12 +788,24 @@
         mPrefDevDispatchers.unregister(dispatcher);
     }
 
+    /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
+        mNonDefDevDispatchers.register(dispatcher);
+    }
+
+    /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
+        mNonDefDevDispatchers.unregister(dispatcher);
+    }
+
     /*package*/ int setPreferredDevicesForCapturePresetSync(
             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
-        final long identity = Binder.clearCallingIdentity();
-        final int status = mAudioSystem.setDevicesRoleForCapturePreset(
-                capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
-        Binder.restoreCallingIdentity(identity);
+        int status = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            status = mAudioSystem.setDevicesRoleForCapturePreset(
+                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+        }
 
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
@@ -691,10 +814,12 @@
     }
 
     /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
-        final long identity = Binder.clearCallingIdentity();
-        final int status = mAudioSystem.clearDevicesRoleForCapturePreset(
-                capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
-        Binder.restoreCallingIdentity(identity);
+        int status  = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            status = mAudioSystem.clearDevicesRoleForCapturePreset(
+                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+        }
 
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
@@ -1523,6 +1648,19 @@
         mPrefDevDispatchers.finishBroadcast();
     }
 
+    private void dispatchNonDefaultDevice(int strategy,
+                                          @NonNull List<AudioDeviceAttributes> devices) {
+        final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast();
+        for (int i = 0; i < nbDispatchers; i++) {
+            try {
+                mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
+                        strategy, devices);
+            } catch (RemoteException e) {
+            }
+        }
+        mNonDefDevDispatchers.finishBroadcast();
+    }
+
     private void dispatchDevicesRoleForCapturePreset(
             int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
         final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 43c8032d..24c7d2c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -116,6 +116,7 @@
 import android.media.ISpatializerHeadTrackerAvailableCallback;
 import android.media.ISpatializerHeadTrackingModeCallback;
 import android.media.ISpatializerOutputCallback;
+import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IVolumeController;
 import android.media.MediaMetrics;
@@ -2800,11 +2801,12 @@
      * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
      *                                                  List<AudioDeviceAttributes>)
      */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public int setPreferredDevicesForStrategy(int strategy, List<AudioDeviceAttributes> devices) {
+        super.setPreferredDevicesForStrategy_enforcePermission();
         if (devices == null) {
             return AudioSystem.ERROR;
         }
-        enforceModifyAudioRoutingPermission();
         final String logString = String.format(
                 "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy,
@@ -2862,6 +2864,81 @@
         }
     }
 
+    /**
+     * @see AudioManager#setDeviceAsNonDefaultForStrategy(AudioProductStrategy,
+     *                                                    AudioDeviceAttributes)
+     * @see AudioManager#setDeviceAsNonDefaultForStrategy(AudioProductStrategy,
+     *                                                     List<AudioDeviceAttributes>)
+     */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int setDeviceAsNonDefaultForStrategy(int strategy,
+                                                @NonNull AudioDeviceAttributes device) {
+        super.setDeviceAsNonDefaultForStrategy_enforcePermission();
+        Objects.requireNonNull(device);
+        final String logString = String.format(
+                "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s",
+                Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
+        if (device.getRole() == AudioDeviceAttributes.ROLE_INPUT) {
+            Log.e(TAG, "Unsupported input routing in " + logString);
+            return AudioSystem.ERROR;
+        }
+
+        final int status = mDeviceBroker.setDeviceAsNonDefaultForStrategySync(strategy, device);
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, String.format("Error %d in %s)", status, logString));
+        }
+
+        return status;
+    }
+
+    /**
+     * @see AudioManager#removeDeviceAsNonDefaultForStrategy(AudioProductStrategy,
+     *                                                       AudioDeviceAttributes)
+     */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int removeDeviceAsNonDefaultForStrategy(int strategy,
+                                                   AudioDeviceAttributes device) {
+        super.removeDeviceAsNonDefaultForStrategy_enforcePermission();
+        Objects.requireNonNull(device);
+        final String logString = String.format(
+                "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString());
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
+        if (device.getRole() == AudioDeviceAttributes.ROLE_INPUT) {
+            Log.e(TAG, "Unsupported input routing in " + logString);
+            return AudioSystem.ERROR;
+        }
+
+        final int status = mDeviceBroker.removeDeviceAsNonDefaultForStrategySync(strategy, device);
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, String.format("Error %d in %s)", status, logString));
+        }
+        return status;
+    }
+
+    /**
+     * @see AudioManager#getNonDefaultDevicesForStrategy(AudioProductStrategy)
+     */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public List<AudioDeviceAttributes> getNonDefaultDevicesForStrategy(int strategy) {
+        super.getNonDefaultDevicesForStrategy_enforcePermission();
+        List<AudioDeviceAttributes> devices = new ArrayList<>();
+        int status = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            status = AudioSystem.getDevicesForRoleAndStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+        }
+
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, String.format("Error %d in getNonDefaultDeviceForStrategy(%d)",
+                    status, strategy));
+            return new ArrayList<AudioDeviceAttributes>();
+        } else {
+            return devices;
+        }
+    }
+
     /** @see AudioManager#addOnPreferredDevicesForStrategyChangedListener(
      *               Executor, AudioManager.OnPreferredDevicesForStrategyChangedListener)
      */
@@ -2886,6 +2963,30 @@
         mDeviceBroker.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
+    /** @see AudioManager#addOnNonDefaultDevicesForStrategyChangedListener(
+     *               Executor, AudioManager.OnNonDefaultDevicesForStrategyChangedListener)
+     */
+    public void registerStrategyNonDefaultDevicesDispatcher(
+            @Nullable IStrategyNonDefaultDevicesDispatcher dispatcher) {
+        if (dispatcher == null) {
+            return;
+        }
+        enforceModifyAudioRoutingPermission();
+        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+    }
+
+    /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
+     *               AudioManager.OnNonDefaultDevicesForStrategyChangedListener)
+     */
+    public void unregisterStrategyNonDefaultDevicesDispatcher(
+            @Nullable IStrategyNonDefaultDevicesDispatcher dispatcher) {
+        if (dispatcher == null) {
+            return;
+        }
+        enforceModifyAudioRoutingPermission();
+        mDeviceBroker.unregisterStrategyNonDefaultDevicesDispatcher(dispatcher);
+    }
+
     /**
      * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
      */
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index c176f29..7fefc55 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -279,14 +279,27 @@
     }
 
     /**
-     * Same as {@link AudioSystem#removeDevicesRoleForStrategy(int, int)}
+     * Same as {@link AudioSystem#removeDevicesRoleForStrategy(int, int, List)}
+     * @param strategy
+     * @param role
+     * @param devices
+     * @return
+     */
+    public int removeDevicesRoleForStrategy(int strategy, int role,
+                                            @NonNull List<AudioDeviceAttributes> devices) {
+        invalidateRoutingCache();
+        return AudioSystem.removeDevicesRoleForStrategy(strategy, role, devices);
+    }
+
+    /**
+     * Same as {@link AudioSystem#clearDevicesRoleForStrategy(int, int)}
      * @param strategy
      * @param role
      * @return
      */
-    public int removeDevicesRoleForStrategy(int strategy, int role) {
+    public int clearDevicesRoleForStrategy(int strategy, int role) {
         invalidateRoutingCache();
-        return AudioSystem.removeDevicesRoleForStrategy(strategy, role);
+        return AudioSystem.clearDevicesRoleForStrategy(strategy, role);
     }
 
     /**
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index 3c0fda8..c0a238f 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -307,6 +307,9 @@
     static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
         ProgramIdentifier hwId = new ProgramIdentifier();
         hwId.type = id.getType();
+        if (hwId.type == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+            hwId.type = IdentifierType.DAB_SID_EXT;
+        }
         hwId.value = id.getValue();
         return hwId;
     }
@@ -317,9 +320,49 @@
         if (id.type == IdentifierType.INVALID) {
             return null;
         }
-        return new ProgramSelector.Identifier(id.type, id.value);
+        int idType;
+        if (id.type == IdentifierType.DAB_SID_EXT) {
+            idType = ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
+        } else {
+            idType = id.type;
+        }
+        return new ProgramSelector.Identifier(idType, id.value);
     }
 
+    private static boolean isVendorIdentifierType(int idType) {
+        return idType >= IdentifierType.VENDOR_START && idType <= IdentifierType.VENDOR_END;
+    }
+
+    private static boolean isValidHalProgramSelector(
+            android.hardware.broadcastradio.ProgramSelector sel) {
+        if (sel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ
+                && sel.primaryId.type != IdentifierType.RDS_PI
+                && sel.primaryId.type != IdentifierType.HD_STATION_ID_EXT
+                && sel.primaryId.type != IdentifierType.DAB_SID_EXT
+                && sel.primaryId.type != IdentifierType.DRMO_SERVICE_ID
+                && sel.primaryId.type != IdentifierType.SXM_SERVICE_ID
+                && !isVendorIdentifierType(sel.primaryId.type)) {
+            return false;
+        }
+        if (sel.primaryId.type == IdentifierType.DAB_SID_EXT) {
+            boolean hasEnsemble = false;
+            boolean hasFrequency = false;
+            for (int i = 0; i < sel.secondaryIds.length; i++) {
+                if (sel.secondaryIds[i].type == IdentifierType.DAB_ENSEMBLE) {
+                    hasEnsemble = true;
+                } else if (sel.secondaryIds[i].type == IdentifierType.DAB_FREQUENCY_KHZ) {
+                    hasFrequency = true;
+                }
+                if (hasEnsemble && hasFrequency) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    @Nullable
     static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector(
             ProgramSelector sel) {
         android.hardware.broadcastradio.ProgramSelector hwSel =
@@ -332,6 +375,9 @@
             secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
         }
         hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
+        if (!isValidHalProgramSelector(hwSel)) {
+            return null;
+        }
         return hwSel;
     }
 
@@ -344,7 +390,7 @@
     @Nullable
     static ProgramSelector programSelectorFromHalProgramSelector(
             android.hardware.broadcastradio.ProgramSelector sel) {
-        if (isEmpty(sel)) {
+        if (isEmpty(sel) || !isValidHalProgramSelector(sel)) {
             return null;
         }
 
@@ -432,7 +478,34 @@
         return builder.build();
     }
 
+    private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) {
+        return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI
+                || id.type == IdentifierType.HD_STATION_ID_EXT
+                || id.type == IdentifierType.DAB_SID_EXT
+                || id.type == IdentifierType.DRMO_SERVICE_ID
+                || id.type == IdentifierType.SXM_SERVICE_ID
+                || isVendorIdentifierType(id.type);
+    }
+
+    private static boolean isValidPhysicallyTunedTo(ProgramIdentifier id) {
+        return id.type == IdentifierType.AMFM_FREQUENCY_KHZ
+                || id.type == IdentifierType.DAB_FREQUENCY_KHZ
+                || id.type == IdentifierType.DRMO_FREQUENCY_KHZ
+                || id.type == IdentifierType.SXM_CHANNEL
+                || isVendorIdentifierType(id.type);
+    }
+
+    private static boolean isValidHalProgramInfo(ProgramInfo info) {
+        return isValidHalProgramSelector(info.selector)
+                && isValidLogicallyTunedTo(info.logicallyTunedTo)
+                && isValidPhysicallyTunedTo(info.physicallyTunedTo);
+    }
+
+    @Nullable
     static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) {
+        if (!isValidHalProgramInfo(info)) {
+            return null;
+        }
         Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>();
         if (info.relatedContent != null) {
             for (int i = 0; i < info.relatedContent.length; i++) {
@@ -485,7 +558,14 @@
     static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
         Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
         for (int i = 0; i < chunk.modified.length; i++) {
-            modified.add(programInfoFromHalProgramInfo(chunk.modified[i]));
+            RadioManager.ProgramInfo modifiedInfo =
+                    programInfoFromHalProgramInfo(chunk.modified[i]);
+            if (modifiedInfo == null) {
+                Slogf.w(TAG, "Program info %s in program list chunk is not valid",
+                        chunk.modified[i]);
+                continue;
+            }
+            modified.add(modifiedInfo);
         }
         Set<ProgramSelector.Identifier> removed = new ArraySet<>();
         if (chunk.removed != null) {
@@ -547,10 +627,22 @@
         if (isAtLeastU(targetSdkVersion)) {
             return chunk;
         }
-        Set<RadioManager.ProgramInfo> modified = chunk.getModified();
-        modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion));
-        Set<ProgramSelector.Identifier> removed = chunk.getRemoved();
-        removed.removeIf(id -> isNewIdentifierInU(id));
+        Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+        Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
+        while (modifiedIterator.hasNext()) {
+            RadioManager.ProgramInfo info = modifiedIterator.next();
+            if (programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)) {
+                modified.add(info);
+            }
+        }
+        Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+        Iterator<ProgramSelector.Identifier> removedIterator = chunk.getRemoved().iterator();
+        while (removedIterator.hasNext()) {
+            ProgramSelector.Identifier id = removedIterator.next();
+            if (!isNewIdentifierInU(id)) {
+                removed.add(id);
+            }
+        }
         return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
     }
 
@@ -558,7 +650,7 @@
             Announcement hwAnnouncement) {
         return new android.hardware.radio.Announcement(
                 Objects.requireNonNull(programSelectorFromHalProgramSelector(
-                        hwAnnouncement.selector)),
+                        hwAnnouncement.selector), "Program selector can not be null"),
                 hwAnnouncement.type,
                 vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo)
         );
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
index 095a5fa..39b1354 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -24,6 +24,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -37,6 +38,7 @@
  */
 final class ProgramInfoCache {
 
+    private static final String TAG = "BcRadioAidlSrv.cache";
     /**
      * Maximum number of {@link RadioManager#ProgramInfo} elements that will be put into a
      * ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk
@@ -124,6 +126,10 @@
         for (int i = 0; i < chunk.modified.length; i++) {
             RadioManager.ProgramInfo programInfo =
                     ConversionUtils.programInfoFromHalProgramInfo(chunk.modified[i]);
+            if (programInfo == null) {
+                Slogf.e(TAG, "Program info in program info %s in chunk is not valid",
+                        chunk.modified[i]);
+            }
             mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
         }
         if (chunk.removed != null) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 6193f23..f8c19ae 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -97,10 +97,10 @@
 
         public void onTuneFailed(int result, ProgramSelector programSelector) {
             fireLater(() -> {
+                android.hardware.radio.ProgramSelector csel =
+                        ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
+                int tunerResult = ConversionUtils.halResultToTunerResult(result);
                 synchronized (mLock) {
-                    android.hardware.radio.ProgramSelector csel =
-                            ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
-                    int tunerResult = ConversionUtils.halResultToTunerResult(result);
                     fanoutAidlCallbackLocked((cb, sdkVersion) -> {
                         if (csel != null && !ConversionUtils
                                 .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
@@ -117,10 +117,12 @@
         @Override
         public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
             fireLater(() -> {
+                RadioManager.ProgramInfo currentProgramInfo =
+                        ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+                Objects.requireNonNull(currentProgramInfo,
+                        "Program info from AIDL HAL is invalid");
                 synchronized (mLock) {
-                    mCurrentProgramInfo =
-                            ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
-                    RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
+                    mCurrentProgramInfo = currentProgramInfo;
                     fanoutAidlCallbackLocked((cb, sdkVersion) -> {
                         if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
                                 currentProgramInfo, sdkVersion)) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index d700ed0..fe8c238 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -203,10 +203,15 @@
             Slogf.w(TAG, "Cannot tune on AIDL HAL client from non-current user");
             return;
         }
+        android.hardware.broadcastradio.ProgramSelector hwSel =
+                ConversionUtils.programSelectorToHalProgramSelector(selector);
+        if (hwSel == null) {
+            throw new IllegalArgumentException("tune: INVALID_ARGUMENTS for program selector");
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
-                mService.tune(ConversionUtils.programSelectorToHalProgramSelector(selector));
+                mService.tune(hwSel);
             } catch (RuntimeException ex) {
                 throw ConversionUtils.throwOnError(ex, /* action= */ "tune");
             }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 1217e74..e557b50 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2630,6 +2630,9 @@
         }
     }
 
+    /**
+     * Uniquely identifies a Sensor, with the combination of Type and Name.
+     */
     static class SensorData {
         public String type;
         public String name;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 5dba015..f8d6c5f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -50,7 +50,6 @@
 import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
 import android.sysprop.SurfaceFlingerProperties;
-import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
@@ -70,6 +69,7 @@
 import com.android.server.LocalServices;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.SensorUtils;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -683,14 +683,20 @@
     }
 
     /**
-     * A utility to make this class aware of the new display configs whenever the default display is
-     * changed
+     * Called when the underlying display device of the default display is changed.
+     * Some data in this class relates to the physical display of the device, and so we need to
+     * reload the configurations based on this.
+     * E.g. the brightness sensors and refresh rate capabilities depend on the physical display
+     * device that is being used, so will be reloaded.
+     *
+     * @param displayDeviceConfig configurations relating to the underlying display device.
      */
     public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
         mSettingsObserver.setRefreshRates(displayDeviceConfig,
             /* attemptLoadingFromDeviceConfig= */ true);
         mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
             /* attemptLoadingFromDeviceConfig= */ true);
+        mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
     }
 
     /**
@@ -1739,6 +1745,9 @@
 
         private SensorManager mSensorManager;
         private Sensor mLightSensor;
+        private Sensor mRegisteredLightSensor;
+        private String mLightSensorType;
+        private String mLightSensorName;
         private final LightSensorEventListener mLightSensorListener =
                 new LightSensorEventListener();
         // Take it as low brightness before valid sensor data comes
@@ -1899,17 +1908,8 @@
             return mLowAmbientBrightnessThresholds;
         }
 
-        public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
-            mSensorManager = sensorManager;
-            mLightSensor = lightSensor;
-
-            mSensorManager.registerListener(mLightSensorListener,
-                    mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
-        }
-
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
-            final ContentResolver cr = mContext.getContentResolver();
             mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
 
             // DeviceConfig is accessible after system ready.
@@ -2053,6 +2053,10 @@
                 pw.println("    mAmbientHighBrightnessThresholds: " + d);
             }
 
+            pw.println("    mRegisteredLightSensor: " + mRegisteredLightSensor);
+            pw.println("    mLightSensor: " + mLightSensor);
+            pw.println("    mLightSensorName: " + mLightSensorName);
+            pw.println("    mLightSensorType: " + mLightSensorType);
             mLightSensorListener.dumpLocked(pw);
 
             if (mAmbientFilter != null) {
@@ -2106,27 +2110,9 @@
             }
 
             if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
-                Resources resources = mContext.getResources();
-                String lightSensorType = resources.getString(
-                        com.android.internal.R.string.config_displayLightSensorType);
+                Sensor lightSensor = getLightSensor();
 
-                Sensor lightSensor = null;
-                if (!TextUtils.isEmpty(lightSensorType)) {
-                    List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
-                    for (int i = 0; i < sensors.size(); i++) {
-                        Sensor sensor = sensors.get(i);
-                        if (lightSensorType.equals(sensor.getStringType())) {
-                            lightSensor = sensor;
-                            break;
-                        }
-                    }
-                }
-
-                if (lightSensor == null) {
-                    lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
-                }
-
-                if (lightSensor != null) {
+                if (lightSensor != null && lightSensor != mLightSensor) {
                     final Resources res = mContext.getResources();
 
                     mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
@@ -2137,14 +2123,40 @@
                 mLightSensor = null;
             }
 
+            updateSensorStatus();
             if (mRefreshRateChangeable) {
-                updateSensorStatus();
                 synchronized (mLock) {
                     onBrightnessChangedLocked();
                 }
             }
         }
 
+        private void reloadLightSensor(DisplayDeviceConfig displayDeviceConfig) {
+            reloadLightSensorData(displayDeviceConfig);
+            restartObserver();
+        }
+
+        private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
+            // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
+            // it naturally falls back to the global config.xml.
+            if (displayDeviceConfig != null
+                    && displayDeviceConfig.getAmbientLightSensor() != null) {
+                // This covers both the ddc and the config.xml fallback
+                mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
+                mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
+            } else if (mLightSensorName == null && mLightSensorType == null) {
+                Resources resources = mContext.getResources();
+                mLightSensorType = resources.getString(
+                        com.android.internal.R.string.config_displayLightSensorType);
+                mLightSensorName = "";
+            }
+        }
+
+        private Sensor getLightSensor() {
+            return SensorUtils.findSensor(mSensorManager, mLightSensorType,
+                    mLightSensorName, Sensor.TYPE_LIGHT);
+        }
+
         /**
          * Checks to see if at least one value is positive, in which case it is necessary to listen
          * to value changes.
@@ -2288,17 +2300,36 @@
 
             if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
                      && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
-                mSensorManager.registerListener(mLightSensorListener,
-                        mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
-                if (mLoggingEnabled) {
-                    Slog.d(TAG, "updateSensorStatus: registerListener");
-                }
+                registerLightSensor();
+
             } else {
-                mLightSensorListener.removeCallbacks();
-                mSensorManager.unregisterListener(mLightSensorListener);
-                if (mLoggingEnabled) {
-                    Slog.d(TAG, "updateSensorStatus: unregisterListener");
-                }
+                unregisterSensorListener();
+            }
+        }
+
+        private void registerLightSensor() {
+            if (mRegisteredLightSensor == mLightSensor) {
+                return;
+            }
+
+            if (mRegisteredLightSensor != null) {
+                unregisterSensorListener();
+            }
+
+            mSensorManager.registerListener(mLightSensorListener,
+                    mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+            mRegisteredLightSensor = mLightSensor;
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: registerListener");
+            }
+        }
+
+        private void unregisterSensorListener() {
+            mLightSensorListener.removeCallbacks();
+            mSensorManager.unregisterListener(mLightSensorListener);
+            mRegisteredLightSensor = null;
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: unregisterListener");
             }
         }
 
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 4924ad5..48bc46c 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -33,6 +33,10 @@
      */
     public static Sensor findSensor(SensorManager sensorManager, String sensorType,
             String sensorName, int fallbackType) {
+        if (sensorManager == null) {
+            return null;
+        }
+
         if ("".equals(sensorName) && "".equals(sensorType)) {
             return null;
         }
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
new file mode 100644
index 0000000..11a4294
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.IGrammaticalInflectionManager;
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * The implementation of IGrammaticalInflectionManager.aidl.
+ *
+ * <p>This service is API entry point for storing app-specific grammatical inflection.
+ */
+public class GrammaticalInflectionService extends SystemService {
+
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     *
+     * @hide
+     */
+    public GrammaticalInflectionService(Context context) {
+        super(context);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+    }
+
+    private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+        @Override
+        public void setRequestedApplicationGrammaticalGender(
+                String appPackageName, int userId, int gender) {
+            GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
+                    appPackageName, userId, gender);
+        }
+    };
+
+    private void setRequestedApplicationGrammaticalGender(
+            String appPackageName, int userId, int gender) {
+        final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+                mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
+                        userId);
+
+        updater.setGrammaticalGender(gender).commit();
+    }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index d76da83..b8eb901 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -385,8 +385,10 @@
                                     R.styleable.KeyboardLayout_keyboardLayout,
                                     0);
                             String languageTags = a.getString(
-                                    R.styleable.KeyboardLayout_locale);
+                                    R.styleable.KeyboardLayout_keyboardLocale);
                             LocaleList locales = getLocalesFromLanguageTags(languageTags);
+                            int layoutType = a.getInt(R.styleable.KeyboardLayout_keyboardLayoutType,
+                                    0);
                             int vid = a.getInt(
                                     R.styleable.KeyboardLayout_vendorId, -1);
                             int pid = a.getInt(
@@ -403,7 +405,7 @@
                                 if (keyboardName == null || name.equals(keyboardName)) {
                                     KeyboardLayout layout = new KeyboardLayout(
                                             descriptor, label, collection, priority,
-                                            locales, vid, pid);
+                                            locales, layoutType, vid, pid);
                                     visitor.visitKeyboardLayout(
                                             resources, keyboardLayoutResId, layout);
                                 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 89bc495a..121b7c8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -173,7 +173,6 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
-import java.util.Random;
 import java.util.Set;
 import java.util.StringJoiner;
 import java.util.concurrent.CountDownLatch;
@@ -234,7 +233,6 @@
     private final SynchronizedStrongAuthTracker mStrongAuthTracker;
     private final BiometricDeferredQueue mBiometricDeferredQueue;
     private final LongSparseArray<byte[]> mGatekeeperPasswords;
-    private final Random mRandom;
 
     private final NotificationManager mNotificationManager;
     protected final UserManager mUserManager;
@@ -348,23 +346,17 @@
     }
 
     private LockscreenCredential generateRandomProfilePassword() {
-        byte[] randomLockSeed = new byte[] {};
-        try {
-            randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
-            char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
-            byte[] newPassword = new byte[newPasswordChars.length];
-            for (int i = 0; i < newPasswordChars.length; i++) {
-                newPassword[i] = (byte) newPasswordChars[i];
-            }
-            LockscreenCredential credential =
-                    LockscreenCredential.createManagedPassword(newPassword);
-            Arrays.fill(newPasswordChars, '\u0000');
-            Arrays.fill(newPassword, (byte) 0);
-            Arrays.fill(randomLockSeed, (byte) 0);
-            return credential;
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("Fail to generate profile password", e);
+        byte[] randomLockSeed = SecureRandomUtils.randomBytes(40);
+        char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
+        byte[] newPassword = new byte[newPasswordChars.length];
+        for (int i = 0; i < newPasswordChars.length; i++) {
+            newPassword[i] = (byte) newPasswordChars[i];
         }
+        LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
+        Arrays.fill(newPasswordChars, '\u0000');
+        Arrays.fill(newPassword, (byte) 0);
+        Arrays.fill(randomLockSeed, (byte) 0);
+        return credential;
     }
 
     /**
@@ -597,7 +589,6 @@
         mStrongAuthTracker = injector.getStrongAuthTracker();
         mStrongAuthTracker.register(mStrongAuth);
         mGatekeeperPasswords = new LongSparseArray<>();
-        mRandom = new SecureRandom();
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
         mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
@@ -1752,14 +1743,9 @@
     private String getSalt(int userId) {
         long salt = getLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 0, userId);
         if (salt == 0) {
-            try {
-                salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
-                setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
-                Slog.v(TAG, "Initialized lock password salt for user: " + userId);
-            } catch (NoSuchAlgorithmException e) {
-                // Throw an exception rather than storing a password we'll never be able to recover
-                throw new IllegalStateException("Couldn't get SecureRandom number", e);
-            }
+            salt = SecureRandomUtils.randomLong();
+            setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
+            Slog.v(TAG, "Initialized lock password salt for user: " + userId);
         }
         return Long.toHexString(salt);
     }
@@ -2644,7 +2630,7 @@
 
         synchronized (mGatekeeperPasswords) {
             while (handle == 0L || mGatekeeperPasswords.get(handle) != null) {
-                handle = mRandom.nextLong();
+                handle = SecureRandomUtils.randomLong();
             }
             mGatekeeperPasswords.put(handle, gatekeeperPassword);
         }
diff --git a/services/core/java/com/android/server/locksettings/SecureRandomUtils.java b/services/core/java/com/android/server/locksettings/SecureRandomUtils.java
new file mode 100644
index 0000000..4ba4dd0
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/SecureRandomUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.locksettings;
+
+import java.security.SecureRandom;
+
+/** Utilities using a static SecureRandom */
+public class SecureRandomUtils {
+    private static final SecureRandom RNG = new SecureRandom();
+
+    /** Use SecureRandom to generate `length` random bytes */
+    public static byte[] randomBytes(int length) {
+        byte[] res = new byte[length];
+        RNG.nextBytes(res);
+        return res;
+    }
+
+    /** Use SecureRandom to generate a random long */
+    public static long randomLong() {
+        return RNG.nextLong();
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index ad2fa22..cd972dc 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -59,8 +59,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -284,8 +282,10 @@
          */
         static SyntheticPassword create() {
             SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
-            byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
-            byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+            byte[] escrowSplit0 =
+                    SecureRandomUtils.randomBytes(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+            byte[] escrowSplit1 =
+                    SecureRandomUtils.randomBytes(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
             result.recreate(escrowSplit0, escrowSplit1);
             byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
                     PERSONALIZATION_E0, escrowSplit0);
@@ -347,7 +347,7 @@
             result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
             result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
             result.credentialType = credentialType;
-            result.salt = secureRandom(PASSWORD_SALT_LENGTH);
+            result.salt = SecureRandomUtils.randomBytes(PASSWORD_SALT_LENGTH);
             return result;
         }
 
@@ -490,7 +490,7 @@
             android.hardware.weaver.V1_0.IWeaver hidlWeaver = getWeaverHidlService();
             if (hidlWeaver != null) {
                 Slog.i(TAG, "Using HIDL weaver service");
-                return new WeaverHidlWrapper(hidlWeaver);
+                return new WeaverHidlAdapter(hidlWeaver);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to get HIDL weaver service.", e);
@@ -552,7 +552,7 @@
             throw new IllegalArgumentException("Invalid key size for weaver");
         }
         if (value == null) {
-            value = secureRandom(mWeaverConfig.valueSize);
+            value = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
         }
         try {
             mWeaver.write(slot, key, value);
@@ -1039,9 +1039,9 @@
         }
         TokenData tokenData = new TokenData();
         tokenData.mType = type;
-        final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
+        final byte[] secdiscardable = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
         if (isWeaverAvailable()) {
-            tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
+            tokenData.weaverSecret = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
             tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
                             PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
         } else {
@@ -1510,7 +1510,7 @@
      * been created.
      */
     private byte[] createSecdiscardable(long protectorId, int userId) {
-        byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
+        byte[] data = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
         saveSecdiscardable(protectorId, data, userId);
         return data;
     }
@@ -1624,12 +1624,12 @@
     }
 
     private static long generateProtectorId() {
-        SecureRandom rng = new SecureRandom();
-        long result;
-        do {
-            result = rng.nextLong();
-        } while (result == NULL_PROTECTOR_ID);
-        return result;
+        while (true) {
+            final long result = SecureRandomUtils.randomLong();
+            if (result != NULL_PROTECTOR_ID) {
+                return result;
+            }
+        }
     }
 
     @VisibleForTesting
@@ -1637,15 +1637,6 @@
         return 100000 + userId;
     }
 
-    protected static byte[] secureRandom(int length) {
-        try {
-            return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
-        } catch (NoSuchAlgorithmException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
     private String getProtectorKeyAlias(long protectorId) {
         return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
     }
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java b/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java
new file mode 100644
index 0000000..2e9c3fd
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 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.locksettings;
+
+import android.hardware.weaver.IWeaver;
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+import android.hardware.weaver.WeaverReadStatus;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Adapt the legacy HIDL interface to present the AIDL interface.
+ */
+class WeaverHidlAdapter implements IWeaver {
+    private static final String TAG = "WeaverHidlAdapter";
+    private final android.hardware.weaver.V1_0.IWeaver mImpl;
+
+    WeaverHidlAdapter(android.hardware.weaver.V1_0.IWeaver impl) {
+        mImpl = impl;
+    }
+
+    @Override
+    public WeaverConfig getConfig() throws RemoteException {
+        final WeaverConfig[] res = new WeaverConfig[1];
+        mImpl.getConfig((int status, android.hardware.weaver.V1_0.WeaverConfig config) -> {
+            if (status == android.hardware.weaver.V1_0.WeaverStatus.OK) {
+                WeaverConfig aidlRes = new WeaverConfig();
+                aidlRes.slots = config.slots;
+                aidlRes.keySize = config.keySize;
+                aidlRes.valueSize = config.valueSize;
+                res[0] = aidlRes;
+            } else {
+                Slog.e(TAG,
+                        "Failed to get HIDL weaver config. status: " + status
+                                + ", slots: " + config.slots);
+            }
+        });
+        return res[0];
+    }
+
+    @Override
+    public WeaverReadResponse read(int slotId, byte[] key)
+            throws RemoteException {
+        final WeaverReadResponse[] res = new WeaverReadResponse[1];
+        mImpl.read(
+                slotId, toByteArrayList(key),
+                (int inStatus, android.hardware.weaver.V1_0.WeaverReadResponse readResponse) -> {
+                    WeaverReadResponse aidlRes =
+                            new WeaverReadResponse();
+                    switch (inStatus) {
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.OK:
+                            aidlRes.status = WeaverReadStatus.OK;
+                            break;
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.THROTTLE:
+                            aidlRes.status = WeaverReadStatus.THROTTLE;
+                            break;
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.INCORRECT_KEY:
+                            aidlRes.status = WeaverReadStatus.INCORRECT_KEY;
+                            break;
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.FAILED:
+                            aidlRes.status = WeaverReadStatus.FAILED;
+                            break;
+                        default:
+                            Slog.e(TAG, "Unexpected status in read: " + inStatus);
+                            aidlRes.status = WeaverReadStatus.FAILED;
+                            break;
+                    }
+                    aidlRes.timeout = readResponse.timeout;
+                    aidlRes.value = fromByteArrayList(readResponse.value);
+                    res[0] = aidlRes;
+                });
+        return res[0];
+    }
+
+    @Override
+    public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
+        int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
+        if (writeStatus != android.hardware.weaver.V1_0.WeaverStatus.OK) {
+            throw new ServiceSpecificException(
+                    IWeaver.STATUS_FAILED, "Failed IWeaver.write call, status: " + writeStatus);
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        // We do not require the interface hash as the client.
+        throw new UnsupportedOperationException(
+                "WeaverHidlAdapter does not support getInterfaceHash");
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        // Supports only V2 which is at feature parity.
+        return 2;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        // There is no IHwBinder to IBinder. Not required as the client.
+        throw new UnsupportedOperationException("WeaverHidlAdapter does not support asBinder");
+    }
+
+    private static ArrayList<Byte> toByteArrayList(byte[] data) {
+        ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+        for (int i = 0; i < data.length; i++) {
+            result.add(data[i]);
+        }
+        return result;
+    }
+
+    private static byte[] fromByteArrayList(ArrayList<Byte> data) {
+        byte[] result = new byte[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            result[i] = data.get(i);
+        }
+        return result;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
deleted file mode 100644
index 9d93c3d..0000000
--- a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 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.locksettings;
-
-import android.hardware.weaver.V1_0.IWeaver;
-import android.hardware.weaver.V1_0.WeaverConfig;
-import android.hardware.weaver.V1_0.WeaverReadResponse;
-import android.hardware.weaver.V1_0.WeaverReadStatus;
-import android.hardware.weaver.V1_0.WeaverStatus;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Slog;
-
-import java.util.ArrayList;
-
-/**
- * Implement the AIDL IWeaver interface wrapping the HIDL implementation
- */
-class WeaverHidlWrapper implements android.hardware.weaver.IWeaver {
-    private static final String TAG = "WeaverHidlWrapper";
-    private final IWeaver mImpl;
-
-    WeaverHidlWrapper(IWeaver impl) {
-        mImpl = impl;
-    }
-
-    private static ArrayList<Byte> toByteArrayList(byte[] data) {
-        ArrayList<Byte> result = new ArrayList<Byte>(data.length);
-        for (int i = 0; i < data.length; i++) {
-            result.add(data[i]);
-        }
-        return result;
-    }
-
-    private static byte[] fromByteArrayList(ArrayList<Byte> data) {
-        byte[] result = new byte[data.size()];
-        for (int i = 0; i < data.size(); i++) {
-            result[i] = data.get(i);
-        }
-        return result;
-    }
-
-    @Override
-    public String getInterfaceHash() {
-        // We do not require the interface hash as the client.
-        throw new UnsupportedOperationException(
-            "WeaverHidlWrapper does not support getInterfaceHash");
-    }
-    @Override
-    public int getInterfaceVersion() {
-        // Supports only V2 which is at feature parity.
-        return 2;
-    }
-    @Override
-    public android.os.IBinder asBinder() {
-        // There is no IHwBinder to IBinder. Not required as the client.
-        throw new UnsupportedOperationException("WeaverHidlWrapper does not support asBinder");
-    }
-
-    @Override
-    public android.hardware.weaver.WeaverConfig getConfig() throws RemoteException {
-        final WeaverConfig[] res = new WeaverConfig[1];
-        mImpl.getConfig((int status, WeaverConfig config) -> {
-            if (status == WeaverStatus.OK && config.slots > 0) {
-                res[0] = config;
-            } else {
-                res[0] = null;
-                Slog.e(TAG,
-                        "Failed to get HIDL weaver config. status: " + status
-                                + ", slots: " + config.slots);
-            }
-        });
-
-        if (res[0] == null) {
-            return null;
-        }
-        android.hardware.weaver.WeaverConfig config = new android.hardware.weaver.WeaverConfig();
-        config.slots = res[0].slots;
-        config.keySize = res[0].keySize;
-        config.valueSize = res[0].valueSize;
-        return config;
-    }
-
-    @Override
-    public android.hardware.weaver.WeaverReadResponse read(int slotId, byte[] key)
-            throws RemoteException {
-        final WeaverReadResponse[] res = new WeaverReadResponse[1];
-        final int[] status = new int[1];
-        mImpl.read(
-                slotId, toByteArrayList(key), (int inStatus, WeaverReadResponse readResponse) -> {
-                    status[0] = inStatus;
-                    res[0] = readResponse;
-                });
-
-        android.hardware.weaver.WeaverReadResponse aidlRes =
-                new android.hardware.weaver.WeaverReadResponse();
-        switch (status[0]) {
-            case WeaverReadStatus.OK:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.OK;
-                break;
-            case WeaverReadStatus.THROTTLE:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.THROTTLE;
-                break;
-            case WeaverReadStatus.INCORRECT_KEY:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.INCORRECT_KEY;
-                break;
-            case WeaverReadStatus.FAILED:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
-                break;
-            default:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
-                break;
-        }
-        if (res[0] != null) {
-            aidlRes.timeout = res[0].timeout;
-            aidlRes.value = fromByteArrayList(res[0].value);
-        }
-        return aidlRes;
-    }
-
-    @Override
-    public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
-        int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
-        if (writeStatus != WeaverStatus.OK) {
-            throw new ServiceSpecificException(
-                android.hardware.weaver.IWeaver.STATUS_FAILED, "Failed IWeaver.write call");
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 2ef193c..32479ee 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -23,6 +23,7 @@
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.net.ConnectivityManager;
@@ -36,6 +37,7 @@
 import com.android.server.LocalServices;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -157,6 +159,56 @@
         return false;
     }
 
+    private static boolean containsAny(Collection<String> list, Collection<String> which) {
+        if (list.isEmpty()) {
+            return false;
+        }
+        for (var element : which) {
+            if (list.contains(element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addLibraryDependency(ArraySet<String> results, List<String> libPackageNames) {
+        var pmInternal = LocalServices.getService(PackageManagerInternal.class);
+
+        var libraryNames = new ArraySet<String>();
+        var staticSharedLibraryNames = new ArraySet<String>();
+        var sdkLibraryNames = new ArraySet<String>();
+        for (var packageName : libPackageNames) {
+            var pkg = pmInternal.getAndroidPackage(packageName);
+            if (pkg == null) {
+                continue;
+            }
+            libraryNames.addAll(pkg.getLibraryNames());
+            var libraryName = pkg.getStaticSharedLibraryName();
+            if (libraryName != null) {
+                staticSharedLibraryNames.add(libraryName);
+            }
+            libraryName = pkg.getSdkLibraryName();
+            if (libraryName != null) {
+                sdkLibraryNames.add(libraryName);
+            }
+        }
+
+        if (libraryNames.isEmpty()
+                && staticSharedLibraryNames.isEmpty()
+                && sdkLibraryNames.isEmpty()) {
+            return;
+        }
+
+        pmInternal.forEachPackage(pkg -> {
+            if (containsAny(pkg.getUsesLibraries(), libraryNames)
+                    || containsAny(pkg.getUsesOptionalLibraries(), libraryNames)
+                    || containsAny(pkg.getUsesStaticLibraries(), staticSharedLibraryNames)
+                    || containsAny(pkg.getUsesSdkLibraries(), sdkLibraryNames)) {
+                results.add(pkg.getPackageName());
+            }
+        });
+    }
+
     /**
      * True if any app has sent or received network data over the past
      * {@link #ACTIVE_NETWORK_DURATION_MILLIS} milliseconds.
@@ -225,6 +277,7 @@
      */
     public List<String> getDependencyPackages(List<String> packageNames) {
         var results = new ArraySet<String>();
+        // Include packages sharing the same process
         var am = mContext.getSystemService(ActivityManager.class);
         for (var info : am.getRunningAppProcesses()) {
             for (var packageName : packageNames) {
@@ -236,10 +289,14 @@
                 }
             }
         }
+        // Include packages using bounded services
         var amInternal = LocalServices.getService(ActivityManagerInternal.class);
         for (var packageName : packageNames) {
             results.addAll(amInternal.getClientPackages(packageName));
         }
+        // Include packages using libraries
+        addLibraryDependency(results, packageNames);
+
         return new ArrayList<>(results);
     }
 }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index d873736..0a59c19 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2045,7 +2045,8 @@
         }
 
         final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
-                ps.getPkg().getStaticSharedLibraryName(), ps.getPkg().getStaticSharedLibVersion());
+                ps.getPkg().getStaticSharedLibraryName(),
+                ps.getPkg().getStaticSharedLibraryVersion());
         if (libraryInfo == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 9ea1807..3df46a2 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -186,7 +186,7 @@
                 SharedLibraryInfo libraryInfo = null;
                 if (pkg.getStaticSharedLibraryName() != null) {
                     libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibraryName(),
-                            pkg.getStaticSharedLibVersion());
+                            pkg.getStaticSharedLibraryVersion());
                 } else if (pkg.getSdkLibraryName() != null) {
                     libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibraryName(),
                             pkg.getSdkLibVersionMajor());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7049f9a..16bf0fe 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -297,7 +297,7 @@
         SharedUserSetting sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(pkgSetting);
         if (sharedUserSetting != null) {
             sharedUserSetting.addPackage(pkgSetting);
-            if (parsedPackage.isLeavingSharedUid()
+            if (parsedPackage.isLeavingSharedUser()
                     && SharedUidMigration.applyStrategy(BEST_EFFORT)
                     && sharedUserSetting.isSingleUser()) {
                 // Attempt the transparent shared UID migration
@@ -1552,7 +1552,7 @@
                     }
 
                     // APK should not re-join shared UID
-                    if (oldPackage.isLeavingSharedUid() && !parsedPackage.isLeavingSharedUid()) {
+                    if (oldPackage.isLeavingSharedUser() && !parsedPackage.isLeavingSharedUser()) {
                         throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
                                 "Package " + parsedPackage.getPackageName()
                                         + " attempting to rejoin " + newSharedUid);
@@ -3801,7 +3801,7 @@
             if (installedPkgSetting == null || !installedPkgSetting.hasSharedUser()) {
                 // Directly ignore sharedUserSetting for new installs, or if the app has
                 // already left shared UID
-                ignoreSharedUserId = parsedPackage.isLeavingSharedUid();
+                ignoreSharedUserId = parsedPackage.isLeavingSharedUser();
             }
 
             if (!ignoreSharedUserId && parsedPackage.getSharedUserId() != null) {
@@ -4324,10 +4324,10 @@
                 SharedLibraryInfo libInfo = versionedLib.valueAt(i);
                 final long libVersionCode = libInfo.getDeclaringPackage()
                         .getLongVersionCode();
-                if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
+                if (libInfo.getLongVersion() < pkg.getStaticSharedLibraryVersion()) {
                     minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
                 } else if (libInfo.getLongVersion()
-                        > pkg.getStaticSharedLibVersion()) {
+                        > pkg.getStaticSharedLibraryVersion()) {
                     maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
                 } else {
                     minVersionCode = maxVersionCode = libVersionCode;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 9c60795..8c5bab6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1211,7 +1211,8 @@
             // Take a short detour to confirm with user
             final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
             intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
-            intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
+            intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
+                    new PackageManager.UninstallCompleteCallback(adapter.getBinder().asBinder()));
             adapter.onUserActionRequired(intent);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ed4c849..afcd9d1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2579,6 +2579,10 @@
                         : PackageInstaller.ACTION_CONFIRM_INSTALL);
         intent.setPackage(mPm.getPackageInstallerPackageName());
         intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+        synchronized (mLock) {
+            intent.putExtra(PackageInstaller.EXTRA_RESOLVED_BASE_PATH,
+                    mResolvedBaseFile != null ? mResolvedBaseFile.getAbsolutePath() : null);
+        }
 
         sendOnUserActionRequired(mContext, target, sessionId, intent);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a52ed8b..7ab19ff 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1105,8 +1105,9 @@
     @Deprecated
     @NonNull
     public Computer snapshotComputer(boolean allowLiveComputer) {
+        var isHoldingPackageLock = Thread.holdsLock(mLock);
         if (allowLiveComputer) {
-            if (Thread.holdsLock(mLock)) {
+            if (isHoldingPackageLock) {
                 // If the current thread holds mLock then it may have modified state but not
                 // yet invalidated the snapshot.  Always give the thread the live computer.
                 return mLiveComputer;
@@ -1120,6 +1121,15 @@
             return oldSnapshot.use();
         }
 
+        if (isHoldingPackageLock) {
+            // If the current thread holds mLock then it already has exclusive write access to the
+            // two snapshot fields, and we can just go ahead and rebuild the snapshot.
+            @SuppressWarnings("GuardedBy")
+            var newSnapshot = rebuildSnapshot(oldSnapshot, pendingVersion);
+            sSnapshot.set(newSnapshot);
+            return newSnapshot.use();
+        }
+
         synchronized (mSnapshotLock) {
             // Re-capture pending version in case a new invalidation occurred since last check
             var rebuildSnapshot = sSnapshot.get();
@@ -1137,7 +1147,11 @@
                 // Fetch version one last time to ensure that the rebuilt snapshot matches
                 // the latest invalidation, which could have come in between entering the
                 // SnapshotLock and mLock sync blocks.
+                rebuildSnapshot = sSnapshot.get();
                 rebuildVersion = sSnapshotPendingVersion.get();
+                if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
+                    return rebuildSnapshot.use();
+                }
 
                 // Build the snapshot for this version
                 var newSnapshot = rebuildSnapshot(rebuildSnapshot, rebuildVersion);
@@ -1147,7 +1161,7 @@
         }
     }
 
-    @GuardedBy({ "mLock", "mSnapshotLock"})
+    @GuardedBy("mLock")
     private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
         var now = SystemClock.currentTimeMicro();
         var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
@@ -2878,7 +2892,7 @@
     static void renameStaticSharedLibraryPackage(ParsedPackage parsedPackage) {
         // Derive the new package synthetic package name
         parsedPackage.setPackageName(toStaticSharedLibraryPackageName(
-                parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibVersion()));
+                parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibraryVersion()));
     }
 
     private static String toStaticSharedLibraryPackageName(
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index b18179e..433e7a1 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1386,6 +1386,8 @@
         return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
     }
 
+
+
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 9b6bfd9..eb99536 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -234,7 +234,7 @@
         }
         if (pkg.getStaticSharedLibraryName() != null) {
             if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibraryName(),
-                    pkg.getStaticSharedLibVersion())) {
+                    pkg.getStaticSharedLibraryVersion())) {
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 53be787..321c5c6 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -89,6 +89,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -370,6 +371,8 @@
 
     // Current settings file.
     private final File mSettingsFilename;
+    // Reserve copy of the current settings file.
+    private final File mSettingsReserveCopyFilename;
     // Previous settings file.
     // Removed when the current settings file successfully stored.
     private final File mPreviousSettingsFilename;
@@ -640,6 +643,7 @@
         mRuntimePermissionsPersistence = null;
         mPermissionDataProvider = null;
         mSettingsFilename = null;
+        mSettingsReserveCopyFilename = null;
         mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
@@ -711,6 +715,7 @@
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                 -1, -1);
         mSettingsFilename = new File(mSystemDir, "packages.xml");
+        mSettingsReserveCopyFilename = new File(mSystemDir, "packages.xml.reservecopy");
         mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
         mPackageListFilename = new File(mSystemDir, "packages.list");
         FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -752,6 +757,7 @@
         mLock = null;
         mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
         mSettingsFilename = null;
+        mSettingsReserveCopyFilename = null;
         mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
@@ -1455,7 +1461,7 @@
     void checkAndConvertSharedUserSettingsLPw(SharedUserSetting sharedUser) {
         if (!sharedUser.isSingleUser()) return;
         final AndroidPackage pkg = sharedUser.getPackageSettings().valueAt(0).getPkg();
-        if (pkg != null && pkg.isLeavingSharedUid()
+        if (pkg != null && pkg.isLeavingSharedUser()
                 && SharedUidMigration.applyStrategy(BEST_EFFORT)) {
             convertSharedUserSettingsLPw(sharedUser);
         }
@@ -2681,12 +2687,25 @@
 
             // New settings successfully written, old ones are no longer needed.
             mPreviousSettingsFilename.delete();
+            mSettingsReserveCopyFilename.delete();
 
             FileUtils.setPermissions(mSettingsFilename.toString(),
-                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
-                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                     -1, -1);
 
+            try {
+                FileUtils.copy(mSettingsFilename, mSettingsReserveCopyFilename);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to backup settings", e);
+            }
+
+            try {
+                VerityUtils.setUpFsverity(mSettingsFilename.getAbsolutePath());
+                VerityUtils.setUpFsverity(mSettingsReserveCopyFilename.getAbsolutePath());
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to verity-protect settings", e);
+            }
+
             writeKernelMappingLPr();
             writePackageListLPr();
             writeAllUsersPackageRestrictionsLPr(sync);
@@ -3117,49 +3136,62 @@
         }
     }
 
-    boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
-        FileInputStream str = null;
-        if (mPreviousSettingsFilename.exists()) {
-            try {
-                str = new FileInputStream(mPreviousSettingsFilename);
-                mReadMessages.append("Reading from backup settings file\n");
-                PackageManagerService.reportSettingsProblem(Log.INFO,
-                        "Need to read from backup settings file");
-                if (mSettingsFilename.exists()) {
-                    // If both the previous and current settings files exist,
-                    // we ignore the current since it might have been corrupted.
-                    Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
-                            + mSettingsFilename);
-                    mSettingsFilename.delete();
-                }
-            } catch (java.io.IOException e) {
-                // We'll try for the normal settings file.
-            }
-        }
-
+    boolean readSettingsLPw(@NonNull Computer computer, @NonNull List<UserInfo> users,
+            ArrayMap<String, Long> originalFirstInstallTimes) {
         mPendingPackages.clear();
         mPastSignatures.clear();
         mKeySetRefs.clear();
         mInstallerPackages.clear();
+        originalFirstInstallTimes.clear();
 
-        // If any user state doesn't have a first install time, e.g., after an OTA,
-        // use the pre OTA firstInstallTime timestamp. This is because we migrated from per package
-        // firstInstallTime to per user-state. Without this, OTA can cause this info to be lost.
-        final ArrayMap<String, Long> originalFirstInstallTimes = new ArrayMap<>();
+        File file = null;
+        FileInputStream str = null;
 
         try {
-            if (str == null) {
-                if (!mSettingsFilename.exists()) {
-                    mReadMessages.append("No settings file found\n");
+            // Check if the previous write was incomplete.
+            if (mPreviousSettingsFilename.exists()) {
+                try {
+                    file = mPreviousSettingsFilename;
+                    str = new FileInputStream(file);
+                    mReadMessages.append("Reading from backup settings file\n");
                     PackageManagerService.reportSettingsProblem(Log.INFO,
-                            "No settings file; creating initial state");
-                    // It's enough to just touch version details to create them
-                    // with default values
-                    findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
-                    findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
-                    return false;
+                            "Need to read from backup settings file");
+                    if (mSettingsFilename.exists()) {
+                        // If both the previous and current settings files exist,
+                        // we ignore the current since it might have been corrupted.
+                        Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
+                                + mSettingsFilename);
+                        mSettingsFilename.delete();
+                    }
+                    // Ignore reserve copy as well.
+                    mSettingsReserveCopyFilename.delete();
+                } catch (java.io.IOException e) {
+                    // We'll try for the normal settings file.
                 }
-                str = new FileInputStream(mSettingsFilename);
+            }
+            if (str == null) {
+                if (mSettingsFilename.exists()) {
+                    // Using packages.xml.
+                    file = mSettingsFilename;
+                    str = new FileInputStream(file);
+                } else if (mSettingsReserveCopyFilename.exists()) {
+                    // Using reserve copy.
+                    file = mSettingsReserveCopyFilename;
+                    str = new FileInputStream(file);
+                    mReadMessages.append("Reading from reserve copy settings file\n");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "Need to read from reserve copy settings file");
+                }
+            }
+            if (str == null) {
+                // No available data sources.
+                mReadMessages.append("No settings file found\n");
+                PackageManagerService.reportSettingsProblem(Log.INFO,
+                        "No settings file; creating initial state");
+                // Not necessary, but will avoid wtf-s in the "finally" section.
+                findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
+                findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
+                return false;
             }
             final TypedXmlPullParser parser = Xml.resolvePullParser(str);
 
@@ -3280,6 +3312,33 @@
             mReadMessages.append("Error reading: " + e.toString());
             PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
             Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
+
+            // Remove corrupted file and retry.
+            Slog.e(TAG,
+                    "Error reading package manager settings, removing " + file + " and retrying.",
+                    e);
+            file.delete();
+
+            // Ignore the result to not mark this as a "first boot".
+            readSettingsLPw(computer, users, originalFirstInstallTimes);
+        }
+
+        return true;
+    }
+
+    /**
+     * @return false if settings file is missing (i.e. during first boot), true otherwise
+     */
+    boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
+        // If any user state doesn't have a first install time, e.g., after an OTA,
+        // use the pre OTA firstInstallTime timestamp. This is because we migrated from per package
+        // firstInstallTime to per user-state. Without this, OTA can cause this info to be lost.
+        final ArrayMap<String, Long> originalFirstInstallTimes = new ArrayMap<>();
+
+        try {
+            if (!readSettingsLPw(computer, users, originalFirstInstallTimes)) {
+                return false;
+            }
         } finally {
             if (!mVersion.containsKey(StorageManager.UUID_PRIVATE_INTERNAL)) {
                 Slog.wtf(PackageManagerService.TAG,
@@ -4857,7 +4916,7 @@
                 pw.print(prefix); pw.println("  static library:");
                 pw.print(prefix); pw.print("    ");
                 pw.print("name:"); pw.print(pkg.getStaticSharedLibraryName());
-                pw.print(" version:"); pw.println(pkg.getStaticSharedLibVersion());
+                pw.print(" version:"); pw.println(pkg.getStaticSharedLibraryVersion());
             }
 
             if (pkg.getSdkLibraryName() != null) {
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 8c2b212..d2ce23e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -408,7 +408,7 @@
         final int versionCount = versionedLib.size();
         for (int i = 0; i < versionCount; i++) {
             final long libVersion = versionedLib.keyAt(i);
-            if (libVersion < pkg.getStaticSharedLibVersion()) {
+            if (libVersion < pkg.getStaticSharedLibraryVersion()) {
                 previousLibVersion = Math.max(previousLibVersion, libVersion);
             }
         }
@@ -468,7 +468,7 @@
                 }
             } else if (pkg.getStaticSharedLibraryName() != null) {
                 SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
-                        pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibVersion());
+                        pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibraryVersion());
                 if (definedLibrary != null) {
                     action.accept(definedLibrary, libInfo);
                 }
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index a7a4c4e..a037ae8 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -253,7 +253,7 @@
         }
         if (mDisabledPackages.size() == 1) {
             final AndroidPackage pkg = mDisabledPackages.valueAt(0).getPkg();
-            return pkg != null && pkg.isLeavingSharedUid();
+            return pkg != null && pkg.isLeavingSharedUser();
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 87805e0..ff993ea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -131,7 +131,7 @@
         info.splitRevisionCodes = pkg.getSplitRevisionCodes();
         info.versionName = pkg.getVersionName();
         info.sharedUserId = pkg.getSharedUserId();
-        info.sharedUserLabel = pkg.getSharedUserLabel();
+        info.sharedUserLabel = pkg.getSharedUserLabelRes();
         info.applicationInfo = applicationInfo;
         info.installLocation = pkg.getInstallLocation();
         if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
@@ -218,7 +218,7 @@
                     }
                 }
             }
-            if (pkg.areAttributionsUserVisible()) {
+            if (pkg.isAttributionsUserVisible()) {
                 info.applicationInfo.privateFlagsExt
                         |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
             } else {
@@ -869,7 +869,7 @@
     public static int appInfoFlags(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
         // @formatter:off
         int pkgWithoutStateFlags = flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
-                | flag(pkg.isBaseHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
+                | flag(pkg.isHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
                 | flag(pkg.isAllowBackup(), ApplicationInfo.FLAG_ALLOW_BACKUP)
                 | flag(pkg.isKillAfterRestore(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
                 | flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
@@ -972,7 +972,7 @@
         // @formatter:off
         int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
                 | flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
-                | flag(pkg.areAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
+                | flag(pkg.isAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
                 | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK)
                 | flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
         return appInfoPrivateFlagsExt(pkgWithoutStateFlags, pkgSetting);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 4fee84f..f3ee531 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -103,7 +103,7 @@
         return new SharedLibraryInfo(null, pkg.getPackageName(),
                 AndroidPackageUtils.getAllCodePaths(pkg),
                 pkg.getStaticSharedLibraryName(),
-                pkg.getStaticSharedLibVersion(),
+                pkg.getStaticSharedLibraryVersion(),
                 SharedLibraryInfo.TYPE_STATIC,
                 new VersionedPackage(pkg.getManifestPackageName(),
                         pkg.getLongVersionCode()),
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index ba36ab7..e361c93 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -717,7 +717,7 @@
     }
 
     @Override
-    public boolean areAttributionsUserVisible() {
+    public boolean isAttributionsUserVisible() {
         return getBoolean(Booleans.ATTRIBUTIONS_ARE_USER_VISIBLE);
     }
 
@@ -869,7 +869,7 @@
     }
 
     @Override
-    public int getBanner() {
+    public int getBannerRes() {
         return banner;
     }
 
@@ -897,7 +897,7 @@
 
     @Nullable
     @Override
-    public String getClassName() {
+    public String getApplicationClassName() {
         return className;
     }
 
@@ -924,7 +924,7 @@
     }
 
     @Override
-    public int getDataExtractionRules() {
+    public int getDataExtractionRulesRes() {
         return dataExtractionRules;
     }
 
@@ -940,7 +940,7 @@
     }
 
     @Override
-    public int getFullBackupContent() {
+    public int getFullBackupContentRes() {
         return fullBackupContent;
     }
 
@@ -1006,7 +1006,7 @@
     }
 
     @Override
-    public int getLogo() {
+    public int getLogoRes() {
         return logo;
     }
 
@@ -1277,7 +1277,7 @@
     }
 
     @Override
-    public int getSharedUserLabel() {
+    public int getSharedUserLabelRes() {
         return sharedUserLabel;
     }
 
@@ -1330,7 +1330,7 @@
     }
 
     @Override
-    public long getStaticSharedLibVersion() {
+    public long getStaticSharedLibraryVersion() {
         return staticSharedLibVersion;
     }
 
@@ -1356,7 +1356,7 @@
     }
 
     @Override
-    public int getTheme() {
+    public int getThemeRes() {
         return theme;
     }
 
@@ -1519,8 +1519,8 @@
     }
 
     @Override
-    public boolean isBaseHardwareAccelerated() {
-        return getBoolean(Booleans.BASE_HARDWARE_ACCELERATED);
+    public boolean isHardwareAccelerated() {
+        return getBoolean(Booleans.HARDWARE_ACCELERATED);
     }
 
     @Override
@@ -1609,7 +1609,7 @@
     }
 
     @Override
-    public boolean isLeavingSharedUid() {
+    public boolean isLeavingSharedUser() {
         return getBoolean(Booleans.LEAVING_SHARED_UID);
     }
 
@@ -1840,14 +1840,14 @@
     }
 
     @Override
-    public PackageImpl setBanner(int value) {
+    public PackageImpl setBannerRes(int value) {
         banner = value;
         return this;
     }
 
     @Override
-    public PackageImpl setBaseHardwareAccelerated(boolean value) {
-        return setBoolean(Booleans.BASE_HARDWARE_ACCELERATED, value);
+    public PackageImpl setHardwareAccelerated(boolean value) {
+        return setBoolean(Booleans.HARDWARE_ACCELERATED, value);
     }
 
     @Override
@@ -1874,7 +1874,7 @@
     }
 
     @Override
-    public PackageImpl setClassName(@Nullable String className) {
+    public PackageImpl setApplicationClassName(@Nullable String className) {
         this.className = className == null ? null : className.trim();
         return this;
     }
@@ -1903,7 +1903,7 @@
     }
 
     @Override
-    public PackageImpl setDataExtractionRules(int value) {
+    public PackageImpl setDataExtractionRulesRes(int value) {
         dataExtractionRules = value;
         return this;
     }
@@ -1940,7 +1940,7 @@
     }
 
     @Override
-    public PackageImpl setFullBackupContent(int value) {
+    public PackageImpl setFullBackupContentRes(int value) {
         fullBackupContent = value;
         return this;
     }
@@ -2022,7 +2022,7 @@
     }
 
     @Override
-    public PackageImpl setLeavingSharedUid(boolean value) {
+    public PackageImpl setLeavingSharedUser(boolean value) {
         return setBoolean(Booleans.LEAVING_SHARED_UID, value);
     }
 
@@ -2033,7 +2033,7 @@
     }
 
     @Override
-    public PackageImpl setLogo(int value) {
+    public PackageImpl setLogoRes(int value) {
         logo = value;
         return this;
     }
@@ -2292,7 +2292,7 @@
     }
 
     @Override
-    public PackageImpl setSharedUserLabel(int value) {
+    public PackageImpl setSharedUserLabelRes(int value) {
         sharedUserLabel = value;
         return this;
     }
@@ -2318,7 +2318,7 @@
     }
 
     @Override
-    public PackageImpl setStaticSharedLibVersion(long value) {
+    public PackageImpl setStaticSharedLibraryVersion(long value) {
         staticSharedLibVersion = value;
         return this;
     }
@@ -2397,7 +2397,7 @@
     }
 
     @Override
-    public PackageImpl setTheme(int value) {
+    public PackageImpl setThemeRes(int value) {
         theme = value;
         return this;
     }
@@ -3529,7 +3529,7 @@
     private static class Booleans {
         @LongDef({
                 EXTERNAL_STORAGE,
-                BASE_HARDWARE_ACCELERATED,
+                HARDWARE_ACCELERATED,
                 ALLOW_BACKUP,
                 KILL_AFTER_RESTORE,
                 RESTORE_ANY_VERSION,
@@ -3593,7 +3593,7 @@
         public @interface Flags {}
 
         private static final long EXTERNAL_STORAGE = 1L;
-        private static final long BASE_HARDWARE_ACCELERATED = 1L << 1;
+        private static final long HARDWARE_ACCELERATED = 1L << 1;
         private static final long ALLOW_BACKUP = 1L << 2;
         private static final long KILL_AFTER_RESTORE = 1L << 3;
         private static final long RESTORE_ANY_VERSION = 1L << 4;
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 075173d..49f85e9 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -16,9 +16,14 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.Dimension;
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
 import android.annotation.SystemApi;
+import android.annotation.XmlRes;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -86,6 +91,109 @@
 public interface AndroidPackage {
 
     /**
+     * @see ApplicationInfo#className
+     * @see R.styleable#AndroidManifestApplication_name
+     */
+    @Nullable
+    String getApplicationClassName();
+
+    /**
+     * @see ApplicationInfo#appComponentFactory
+     * @see R.styleable#AndroidManifestApplication_appComponentFactory
+     */
+    @Nullable
+    String getAppComponentFactory();
+
+    /**
+     * @see ApplicationInfo#backupAgentName
+     * @see R.styleable#AndroidManifestApplication_backupAgent
+     */
+    @Nullable
+    String getBackupAgentName();
+
+    /**
+     * @see ApplicationInfo#banner
+     * @see R.styleable#AndroidManifestApplication_banner
+     */
+    @DrawableRes
+    int getBannerRes();
+
+    /**
+     * @see PackageInfo#baseRevisionCode
+     * @see R.styleable#AndroidManifest_revisionCode
+     */
+    int getBaseRevisionCode();
+
+    /**
+     * @see ApplicationInfo#category
+     * @see R.styleable#AndroidManifestApplication_appCategory
+     */
+    int getCategory();
+
+    /**
+     * @see ApplicationInfo#classLoaderName
+     * @see R.styleable#AndroidManifestApplication_classLoader
+     */
+    @Nullable
+    String getClassLoaderName();
+
+    /**
+     * @see ApplicationInfo#compatibleWidthLimitDp
+     * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
+     */
+    @Dimension(unit = Dimension.DP)
+    int getCompatibleWidthLimitDp();
+
+    /**
+     * @see ApplicationInfo#dataExtractionRulesRes
+     * @see R.styleable#AndroidManifestApplication_dataExtractionRules
+     */
+    @XmlRes
+    int getDataExtractionRulesRes();
+
+    /**
+     * @see ApplicationInfo#descriptionRes
+     * @see R.styleable#AndroidManifestApplication_description
+     */
+    @StringRes // This is actually format="reference"
+    int getDescriptionRes();
+
+    /**
+     * @see ApplicationInfo#fullBackupContent
+     * @see R.styleable#AndroidManifestApplication_fullBackupContent
+     */
+    @XmlRes
+    int getFullBackupContentRes();
+
+    /**
+     * @see ApplicationInfo#getGwpAsanMode()
+     * @see R.styleable#AndroidManifestApplication_gwpAsanMode
+     */
+    @ApplicationInfo.GwpAsanMode
+    int getGwpAsanMode();
+
+    /**
+     * @see ApplicationInfo#iconRes
+     * @see R.styleable#AndroidManifestApplication_icon
+     */
+    @DrawableRes
+    int getIconRes();
+
+    /**
+     * @see ApplicationInfo#labelRes
+     * @see R.styleable#AndroidManifestApplication_label
+     */
+    @StringRes
+    int getLabelRes();
+
+    /**
+     * @see ApplicationInfo#largestWidthLimitDp
+     * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
+     */
+    @Dimension(unit = Dimension.DP)
+    int getLargestWidthLimitDp();
+
+    /**
      * Library names this package is declared as, for use by other packages with "uses-library".
      *
      * @see R.styleable#AndroidManifestLibrary
@@ -94,12 +202,104 @@
     List<String> getLibraryNames();
 
     /**
+     * @see ApplicationInfo#logo
+     * @see R.styleable#AndroidManifestApplication_logo
+     */
+    @DrawableRes
+    int getLogoRes();
+
+    /**
+     * The resource ID used to provide the application's locales configuration.
+     *
+     * @see R.styleable#AndroidManifestApplication_localeConfig
+     */
+    @XmlRes
+    int getLocaleConfigRes();
+
+    /**
+     * @see PackageInfo#getLongVersionCode()
+     * @see R.styleable#AndroidManifest_versionCode
+     * @see R.styleable#AndroidManifest_versionCodeMajor
+     */
+    long getLongVersionCode();
+
+    /**
+     * @see ApplicationInfo#maxAspectRatio
+     * @see R.styleable#AndroidManifestApplication_maxAspectRatio
+     */
+    float getMaxAspectRatio();
+
+    /**
+     * @see ApplicationInfo#minAspectRatio
+     * @see R.styleable#AndroidManifestApplication_minAspectRatio
+     */
+    float getMinAspectRatio();
+
+    /**
+     * @see ApplicationInfo#getNativeHeapZeroInitialized()
+     * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
+     */
+    @ApplicationInfo.NativeHeapZeroInitialized
+    int getNativeHeapZeroInitialized();
+
+    /**
+     * @see ApplicationInfo#networkSecurityConfigRes
+     * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
+     */
+    @XmlRes
+    int getNetworkSecurityConfigRes();
+
+    /**
+     * @see PackageInfo#requiredAccountType
+     * @see R.styleable#AndroidManifestApplication_requiredAccountType
+     */
+    @Nullable
+    String getRequiredAccountType();
+
+    /**
+     * @see ApplicationInfo#requiresSmallestWidthDp
+     * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
+     */
+    @Dimension(unit = Dimension.DP)
+    int getRequiresSmallestWidthDp();
+
+    /**
+     * The restricted account authenticator type that is used by this application.
+     *
+     * @see PackageInfo#restrictedAccountType
+     * @see R.styleable#AndroidManifestApplication_restrictedAccountType
+     */
+    @Nullable
+    String getRestrictedAccountType();
+
+    /**
+     * @see ApplicationInfo#roundIconRes
+     * @see R.styleable#AndroidManifestApplication_roundIcon
+     */
+    @DrawableRes
+    int getRoundIconRes();
+
+    /**
      * @see R.styleable#AndroidManifestSdkLibrary_name
      */
     @Nullable
     String getSdkLibraryName();
 
     /**
+     * @see PackageInfo#sharedUserId
+     * @see R.styleable#AndroidManifest_sharedUserId
+     */
+    @Nullable
+    String getSharedUserId();
+
+    /**
+     * @see PackageInfo#sharedUserLabel
+     * @see R.styleable#AndroidManifest_sharedUserLabel
+     */
+    @StringRes
+    int getSharedUserLabelRes();
+
+    /**
      * @return List of all splits for a package. Note that base.apk is considered a
      * split and will be provided as index 0 of the list.
      */
@@ -113,6 +313,12 @@
     String getStaticSharedLibraryName();
 
     /**
+     * @see R.styleable#AndroidManifestStaticLibrary_version
+     * @hide
+     */
+    long getStaticSharedLibraryVersion();
+
+    /**
      * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
      * package was installed.
      */
@@ -126,23 +332,319 @@
     int getTargetSdkVersion();
 
     /**
+     * @see ApplicationInfo#theme
+     * @see R.styleable#AndroidManifestApplication_theme
+     */
+    @StyleRes
+    int getThemeRes();
+
+    /**
+     * @see ApplicationInfo#uiOptions
+     * @see R.styleable#AndroidManifestApplication_uiOptions
+     */
+    int getUiOptions();
+
+    /**
+     * @see PackageInfo#versionName
+     */
+    @Nullable
+    String getVersionName();
+
+    /**
+     * @see ApplicationInfo#zygotePreloadName
+     * @see R.styleable#AndroidManifestApplication_zygotePreloadName
+     */
+    @Nullable
+    String getZygotePreloadName();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
+     * @see R.styleable#AndroidManifestApplication_allowAudioPlaybackCapture
+     */
+    boolean isAllowAudioPlaybackCapture();
+
+    /**
+     * @see ApplicationInfo#FLAG_ALLOW_BACKUP
+     * @see R.styleable#AndroidManifestApplication_allowBackup
+     */
+    boolean isAllowBackup();
+
+    /**
+     * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
+     * @see R.styleable#AndroidManifestApplication_allowClearUserData
+     */
+    boolean isAllowClearUserData();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
+     * @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
+     */
+    boolean isAllowClearUserDataOnFailedRestore();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
+     * @see R.styleable#AndroidManifestApplication_allowNativeHeapPointerTagging
+     */
+    boolean isAllowNativeHeapPointerTagging();
+
+    /**
+     * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
+     * @see R.styleable#AndroidManifestApplication_allowTaskReparenting
+     */
+    boolean isAllowTaskReparenting();
+
+    /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#DONUT}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
+     * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
+     */
+    boolean isAnyDensity();
+
+    /**
+     * @see ApplicationInfo#areAttributionsUserVisible()
+     * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
+     */
+    boolean isAttributionsUserVisible();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
+     * @see R.styleable#AndroidManifestApplication_backupInForeground
+     */
+    boolean isBackupInForeground();
+
+    /**
+     * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
+     * @see R.styleable#AndroidManifestApplication_hardwareAccelerated
+     */
+    boolean isHardwareAccelerated();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
+     * @see R.styleable#AndroidManifestApplication_cantSaveState
+     */
+    boolean isCantSaveState();
+
+    /**
+     * @see PackageInfo#coreApp
+     */
+    boolean isCoreApp();
+
+    /**
+     * @see ApplicationInfo#crossProfile
+     * @see R.styleable#AndroidManifestApplication_crossProfile
+     */
+    boolean isCrossProfile();
+
+    /**
      * @see ApplicationInfo#FLAG_DEBUGGABLE
      * @see R.styleable#AndroidManifestApplication_debuggable
      */
     boolean isDebuggable();
 
     /**
+     * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
+     * @see R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage
+     */
+    boolean isDefaultToDeviceProtectedStorage();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
+     * @see R.styleable#AndroidManifestApplication_directBootAware
+     */
+    boolean isDirectBootAware();
+
+    /**
+     * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
+     * @see R.styleable#AndroidManifestApplication_extractNativeLibs
+     */
+    boolean isExtractNativeLibs();
+
+    /**
+     * @see ApplicationInfo#FLAG_FACTORY_TEST
+     */
+    boolean isFactoryTest();
+
+    /**
+     * @see R.styleable#AndroidManifestApplication_forceQueryable
+     */
+    boolean isForceQueryable();
+
+    /**
+     * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
+     * @see R.styleable#AndroidManifestApplication_fullBackupOnly
+     */
+    boolean isFullBackupOnly();
+
+    /**
+     * @see ApplicationInfo#FLAG_HAS_CODE
+     * @see R.styleable#AndroidManifestApplication_hasCode
+     */
+    boolean isHasCode();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
+     * @see R.styleable#AndroidManifestApplication_hasFragileUserData
+     */
+    boolean isHasFragileUserData();
+
+    /**
      * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
      * @see R.styleable#AndroidManifest_isolatedSplits
      */
     boolean isIsolatedSplitLoading();
 
     /**
+     * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
+     * @see R.styleable#AndroidManifestApplication_killAfterRestore
+     */
+    boolean isKillAfterRestore();
+
+    /**
+     * @see ApplicationInfo#FLAG_LARGE_HEAP
+     * @see R.styleable#AndroidManifestApplication_largeHeap
+     */
+    boolean isLargeHeap();
+
+    /**
+     * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
+     * smaller than the current SDK version, indicating the package wants to leave its declared
+     * {@link #getSharedUserId()}. This only occurs on new installs, pretending the app never
+     * declared one.
+     *
+     * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
+     */
+    boolean isLeavingSharedUser();
+
+    /**
+     * @see ApplicationInfo#FLAG_MULTIARCH
+     * @see R.styleable#AndroidManifestApplication_multiArch
+     */
+    boolean isMultiArch();
+
+    /**
+     * @see ApplicationInfo#nativeLibraryRootRequiresIsa
+     */
+    boolean isNativeLibraryRootRequiresIsa();
+
+    /**
+     * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
+     */
+    boolean isOnBackInvokedCallbackEnabled();
+
+    /**
+     * @see ApplicationInfo#FLAG_PERSISTENT
+     * @see R.styleable#AndroidManifestApplication_persistent
+     */
+    boolean isPersistent();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
+     * @see R.styleable#AndroidManifestProfileable
+     */
+    boolean isProfileable();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
+     * @see R.styleable#AndroidManifestProfileable_shell
+     */
+    boolean isProfileableByShell();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
+     * @see R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+     */
+    boolean isRequestLegacyExternalStorage();
+
+    /**
+     * @see PackageInfo#requiredForAllUsers
+     * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
+     */
+    boolean isRequiredForAllUsers();
+
+    /**
+     * Whether the enabled settings of components in the application should be reset to the default,
+     * when the application's user data is cleared.
+     *
+     * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
+     */
+    boolean isResetEnabledSettingsOnAppDataCleared();
+
+    /**
+     * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
+     * @see R.styleable#AndroidManifestApplication_restoreAnyVersion
+     */
+    boolean isRestoreAnyVersion();
+
+    /**
      * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
      */
     boolean isSignedWithPlatformKey();
 
     /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#GINGERBREAD}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
+     */
+    boolean isSupportsExtraLargeScreens();
+
+    /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#DONUT}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
+     */
+    boolean isSupportsLargeScreens();
+
+    /**
+     * If omitted from manifest, returns true.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
+     */
+    boolean isSupportsNormalScreens();
+
+    /**
+     * @see ApplicationInfo#FLAG_SUPPORTS_RTL
+     * @see R.styleable#AndroidManifestApplication_supportsRtl
+     */
+    boolean isSupportsRtl();
+
+    /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#DONUT}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
+     */
+    boolean isSupportsSmallScreens();
+
+    /**
+     * @see ApplicationInfo#FLAG_TEST_ONLY
+     * @see R.styleable#AndroidManifestApplication_testOnly
+     */
+    boolean isTestOnly();
+
+    /**
+     * The install time abi override to choose 32bit abi's when multiple abi's are present. This is
+     * only meaningful for multiarch applications. The use32bitAbi attribute is ignored if
+     * cpuAbiOverride is also set.
+     *
+     * @see R.attr#use32bitAbi
+     */
+    boolean isUse32BitAbi();
+
+    /**
+     * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
+     * @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
+     */
+    boolean isUsesCleartextTraffic();
+
+    /**
      * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
      * @see R.styleable#AndroidManifestApplication_useEmbeddedDex
      */
@@ -163,14 +665,6 @@
     // Methods below this comment are not yet exposed as API
 
     /**
-     * @see ApplicationInfo#areAttributionsUserVisible()
-     * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
-     * @hide
-     */
-    @Nullable
-    boolean areAttributionsUserVisible();
-
-    /**
      * Set of Activities parsed from the manifest.
      * <p>
      * This contains minimal system state and does not
@@ -207,14 +701,6 @@
     List<ParsedApexSystemService> getApexSystemServices();
 
     /**
-     * @see ApplicationInfo#appComponentFactory
-     * @see R.styleable#AndroidManifestApplication_appComponentFactory
-     * @hide
-     */
-    @Nullable
-    String getAppComponentFactory();
-
-    /**
      * @see R.styleable#AndroidManifestAttribution
      * @hide
      */
@@ -232,21 +718,6 @@
     int getAutoRevokePermissions();
 
     /**
-     * @see ApplicationInfo#backupAgentName
-     * @see R.styleable#AndroidManifestApplication_backupAgent
-     * @hide
-     */
-    @Nullable
-    String getBackupAgentName();
-
-    /**
-     * @see ApplicationInfo#banner
-     * @see R.styleable#AndroidManifestApplication_banner
-     * @hide
-     */
-    int getBanner();
-
-    /**
      * @see ApplicationInfo#sourceDir
      * @see ApplicationInfo#getBaseCodePath
      *
@@ -259,43 +730,6 @@
     String getBaseApkPath();
 
     /**
-     * @see PackageInfo#baseRevisionCode
-     * @see R.styleable#AndroidManifest_revisionCode
-     * @hide
-     */
-    int getBaseRevisionCode();
-
-    /**
-     * @see ApplicationInfo#category
-     * @see R.styleable#AndroidManifestApplication_appCategory
-     * @hide
-     */
-    int getCategory();
-
-    /**
-     * @see ApplicationInfo#classLoaderName
-     * @see R.styleable#AndroidManifestApplication_classLoader
-     * @hide
-     */
-    @Nullable
-    String getClassLoaderName();
-
-    /**
-     * @see ApplicationInfo#className
-     * @see R.styleable#AndroidManifestApplication_name
-     * @hide
-     */
-    @Nullable
-    String getClassName();
-
-    /**
-     * @see ApplicationInfo#compatibleWidthLimitDp
-     * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
-     * @hide
-     */
-    int getCompatibleWidthLimitDp();
-
-    /**
      * @see ApplicationInfo#compileSdkVersion
      * @see R.styleable#AndroidManifest_compileSdkVersion
      * @hide
@@ -320,20 +754,6 @@
     List<ConfigurationInfo> getConfigPreferences();
 
     /**
-     * @see ApplicationInfo#dataExtractionRulesRes
-     * @see R.styleable#AndroidManifestApplication_dataExtractionRules
-     * @hide
-     */
-    int getDataExtractionRules();
-
-    /**
-     * @see ApplicationInfo#descriptionRes
-     * @see R.styleable#AndroidManifestApplication_description
-     * @hide
-     */
-    int getDescriptionRes();
-
-    /**
      * @see PackageInfo#featureGroups
      * @see R.styleable#AndroidManifestUsesFeature
      * @hide
@@ -343,28 +763,6 @@
     List<FeatureGroupInfo> getFeatureGroups();
 
     /**
-     * @see ApplicationInfo#fullBackupContent
-     * @see R.styleable#AndroidManifestApplication_fullBackupContent
-     * @hide
-     */
-    int getFullBackupContent();
-
-    /**
-     * @see ApplicationInfo#getGwpAsanMode()
-     * @see R.styleable#AndroidManifestApplication_gwpAsanMode
-     * @hide
-     */
-    @ApplicationInfo.GwpAsanMode
-    int getGwpAsanMode();
-
-    /**
-     * @see ApplicationInfo#iconRes
-     * @see R.styleable#AndroidManifestApplication_icon
-     * @hide
-     */
-    int getIconRes();
-
-    /**
      * Permissions requested but not in the manifest. These may have been split or migrated from
      * previous versions/definitions.
      * @hide
@@ -411,43 +809,6 @@
     Set<String> getKnownActivityEmbeddingCerts();
 
     /**
-     * @see ApplicationInfo#labelRes
-     * @see R.styleable#AndroidManifestApplication_label
-     * @hide
-     */
-    int getLabelRes();
-
-    /**
-     * @see ApplicationInfo#largestWidthLimitDp
-     * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
-     * @hide
-     */
-    int getLargestWidthLimitDp();
-
-    /**
-     * The resource ID used to provide the application's locales configuration.
-     *
-     * @see R.styleable#AndroidManifestApplication_localeConfig
-     * @hide
-     */
-    int getLocaleConfigRes();
-
-    /**
-     * @see ApplicationInfo#logo
-     * @see R.styleable#AndroidManifestApplication_logo
-     * @hide
-     */
-    int getLogo();
-
-    /**
-     * @see PackageInfo#getLongVersionCode()
-     * @see R.styleable#AndroidManifest_versionCode
-     * @see R.styleable#AndroidManifest_versionCodeMajor
-     * @hide
-     */
-    long getLongVersionCode();
-
-    /**
      * @see ApplicationInfo#manageSpaceActivityName
      * @see R.styleable#AndroidManifestApplication_manageSpaceActivity
      * @hide
@@ -464,13 +825,6 @@
     String getManifestPackageName();
 
     /**
-     * @see ApplicationInfo#maxAspectRatio
-     * @see R.styleable#AndroidManifestApplication_maxAspectRatio
-     * @hide
-     */
-    float getMaxAspectRatio();
-
-    /**
      * @see R.styleable#AndroidManifestUsesSdk_maxSdkVersion
      * @hide
      */
@@ -501,13 +855,6 @@
     Set<String> getMimeGroups();
 
     /**
-     * @see ApplicationInfo#minAspectRatio
-     * @see R.styleable#AndroidManifestApplication_minAspectRatio
-     * @hide
-     */
-    float getMinAspectRatio();
-
-    /**
      * @see R.styleable#AndroidManifestExtensionSdk
      * @hide
      */
@@ -523,14 +870,6 @@
     int getMinSdkVersion();
 
     /**
-     * @see ApplicationInfo#getNativeHeapZeroInitialized()
-     * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
-     * @hide
-     */
-    @ApplicationInfo.NativeHeapZeroInitialized
-    int getNativeHeapZeroInitialized();
-
-    /**
      * @see ApplicationInfo#nativeLibraryDir
      * @hide
      */
@@ -545,13 +884,6 @@
     String getNativeLibraryRootDir();
 
     /**
-     * @see ApplicationInfo#networkSecurityConfigRes
-     * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
-     * @hide
-     */
-    int getNetworkSecurityConfigRes();
-
-    /**
      * If {@link R.styleable#AndroidManifestApplication_label} is a string literal, this is it.
      * Otherwise, it's stored as {@link #getLabelRes()}.
      *
@@ -787,21 +1119,6 @@
     List<String> getRequestedPermissions();
 
     /**
-     * @see PackageInfo#requiredAccountType
-     * @see R.styleable#AndroidManifestApplication_requiredAccountType
-     * @hide
-     */
-    @Nullable
-    String getRequiredAccountType();
-
-    /**
-     * @see ApplicationInfo#requiresSmallestWidthDp
-     * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
-     * @hide
-     */
-    int getRequiresSmallestWidthDp();
-
-    /**
      * Whether or not the app requested explicitly resizeable Activities. Null value means nothing
      * was explicitly requested.
      *
@@ -824,23 +1141,6 @@
     byte[] getRestrictUpdateHash();
 
     /**
-     * The restricted account authenticator type that is used by this application.
-     *
-     * @see PackageInfo#restrictedAccountType
-     * @see R.styleable#AndroidManifestApplication_restrictedAccountType
-     * @hide
-     */
-    @Nullable
-    String getRestrictedAccountType();
-
-    /**
-     * @see ApplicationInfo#roundIconRes
-     * @see R.styleable#AndroidManifestApplication_roundIcon
-     * @hide
-     */
-    int getRoundIconRes();
-
-    /**
      * @see R.styleable#AndroidManifestSdkLibrary_versionMajor
      * @hide
      */
@@ -872,21 +1172,6 @@
     List<ParsedService> getServices();
 
     /**
-     * @see PackageInfo#sharedUserId
-     * @see R.styleable#AndroidManifest_sharedUserId
-     * @hide
-     */
-    @Nullable
-    String getSharedUserId();
-
-    /**
-     * @see PackageInfo#sharedUserLabel
-     * @see R.styleable#AndroidManifest_sharedUserLabel
-     * @hide
-     */
-    int getSharedUserLabel();
-
-    /**
      * The signature data of all APKs in this package, which must be exactly the same across the
      * base and splits.
      * @hide
@@ -949,12 +1234,6 @@
     int[] getSplitRevisionCodes();
 
     /**
-     * @see R.styleable#AndroidManifestStaticLibrary_version
-     * @hide
-     */
-    long getStaticSharedLibVersion();
-
-    /**
      * @see ApplicationInfo#targetSandboxVersion
      * @see R.styleable#AndroidManifest_targetSandboxVersion
      * @hide
@@ -970,20 +1249,6 @@
     String getTaskAffinity();
 
     /**
-     * @see ApplicationInfo#theme
-     * @see R.styleable#AndroidManifestApplication_theme
-     * @hide
-     */
-    int getTheme();
-
-    /**
-     * @see ApplicationInfo#uiOptions
-     * @see R.styleable#AndroidManifestApplication_uiOptions
-     * @hide
-     */
-    int getUiOptions();
-
-    /**
      * This is an appId, the {@link ApplicationInfo#uid} if the user ID is
      * {@link android.os.UserHandle#SYSTEM}.
      *
@@ -1095,27 +1360,12 @@
     long[] getUsesStaticLibrariesVersions();
 
     /**
-     * @see PackageInfo#versionName
-     * @hide
-     */
-    @Nullable
-    String getVersionName();
-
-    /**
      * @see ApplicationInfo#volumeUuid
      * @hide
      */
     @Nullable
     String getVolumeUuid();
 
-    /**
-     * @see ApplicationInfo#zygotePreloadName
-     * @see R.styleable#AndroidManifestApplication_zygotePreloadName
-     * @hide
-     */
-    @Nullable
-    String getZygotePreloadName();
-
     /** @hide */
     boolean hasPreserveLegacyExternalStorage();
 
@@ -1133,110 +1383,10 @@
      */
     Boolean hasRequestRawExternalStorageAccess();
 
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
-     * @see R.styleable#AndroidManifestApplication_allowAudioPlaybackCapture
-     * @hide
-     */
-    boolean isAllowAudioPlaybackCapture();
-
-    /**
-     * @see ApplicationInfo#FLAG_ALLOW_BACKUP
-     * @see R.styleable#AndroidManifestApplication_allowBackup
-     * @hide
-     */
-    boolean isAllowBackup();
-
-    /**
-     * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
-     * @see R.styleable#AndroidManifestApplication_allowClearUserData
-     * @hide
-     */
-    boolean isAllowClearUserData();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
-     * @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
-     * @hide
-     */
-    boolean isAllowClearUserDataOnFailedRestore();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
-     * @see R.styleable#AndroidManifestApplication_allowNativeHeapPointerTagging
-     * @hide
-     */
-    boolean isAllowNativeHeapPointerTagging();
-
-    /**
-     * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
-     * @see R.styleable#AndroidManifestApplication_allowTaskReparenting
-     * @hide
-     */
-    boolean isAllowTaskReparenting();
-
-    /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#DONUT}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
-     * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
-     * @hide
-     */
-    boolean isAnyDensity();
-
     /** @hide */
     boolean isApex();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
-     * @see R.styleable#AndroidManifestApplication_backupInForeground
-     * @hide
-     */
-    boolean isBackupInForeground();
-
-    /**
-     * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
-     * @see R.styleable#AndroidManifestApplication_hardwareAccelerated
-     * @hide
-     */
-    boolean isBaseHardwareAccelerated();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
-     * @see R.styleable#AndroidManifestApplication_cantSaveState
-     * @hide
-     */
-    boolean isCantSaveState();
-
-    /**
-     * @see PackageInfo#coreApp
-     * @hide
-     */
-    boolean isCoreApp();
-
-    /**
-     * @see ApplicationInfo#crossProfile
-     * @see R.styleable#AndroidManifestApplication_crossProfile
-     * @hide
-     */
-    boolean isCrossProfile();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
-     * @see R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage
-     * @hide
-     */
-    boolean isDefaultToDeviceProtectedStorage();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
-     * @see R.styleable#AndroidManifestApplication_directBootAware
-     * @hide
-     */
-    boolean isDirectBootAware();
-
-    /**
      * @see ApplicationInfo#enabled
      * @see R.styleable#AndroidManifestApplication_enabled
      * @hide
@@ -1250,32 +1400,6 @@
     boolean isExternalStorage();
 
     /**
-     * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
-     * @see R.styleable#AndroidManifestApplication_extractNativeLibs
-     * @hide
-     */
-    boolean isExtractNativeLibs();
-
-    /**
-     * @see ApplicationInfo#FLAG_FACTORY_TEST
-     * @hide
-     */
-    boolean isFactoryTest();
-
-    /**
-     * @see R.styleable#AndroidManifestApplication_forceQueryable
-     * @hide
-     */
-    boolean isForceQueryable();
-
-    /**
-     * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
-     * @see R.styleable#AndroidManifestApplication_fullBackupOnly
-     * @hide
-     */
-    boolean isFullBackupOnly();
-
-    /**
      * @see ApplicationInfo#FLAG_IS_GAME
      * @see R.styleable#AndroidManifestApplication_isGame
      * @hide
@@ -1284,13 +1408,6 @@
     boolean isGame();
 
     /**
-     * @see ApplicationInfo#FLAG_HAS_CODE
-     * @see R.styleable#AndroidManifestApplication_hasCode
-     * @hide
-     */
-    boolean isHasCode();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS
      * @see R.styleable#AndroidManifestIntentFilter
      * @hide
@@ -1298,55 +1415,6 @@
     boolean isHasDomainUrls();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
-     * @see R.styleable#AndroidManifestApplication_hasFragileUserData
-     * @hide
-     */
-    boolean isHasFragileUserData();
-
-    /**
-     * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
-     * @see R.styleable#AndroidManifestApplication_killAfterRestore
-     * @hide
-     */
-    boolean isKillAfterRestore();
-
-    /**
-     * @see ApplicationInfo#FLAG_LARGE_HEAP
-     * @see R.styleable#AndroidManifestApplication_largeHeap
-     * @hide
-     */
-    boolean isLargeHeap();
-
-    /**
-     * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
-     * smaller than the current SDK version.
-     *
-     * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
-     * @hide
-     */
-    boolean isLeavingSharedUid();
-
-    /**
-     * @see ApplicationInfo#FLAG_MULTIARCH
-     * @see R.styleable#AndroidManifestApplication_multiArch
-     * @hide
-     */
-    boolean isMultiArch();
-
-    /**
-     * @see ApplicationInfo#nativeLibraryRootRequiresIsa
-     * @hide
-     */
-    boolean isNativeLibraryRootRequiresIsa();
-
-    /**
-     * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
-     * @hide
-     */
-    boolean isOnBackInvokedCallbackEnabled();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_IS_RESOURCE_OVERLAY
      * @see ApplicationInfo#isResourceOverlay()
      * @see R.styleable#AndroidManifestResourceOverlay
@@ -1371,50 +1439,6 @@
     boolean isPartiallyDirectBootAware();
 
     /**
-     * @see ApplicationInfo#FLAG_PERSISTENT
-     * @see R.styleable#AndroidManifestApplication_persistent
-     * @hide
-     */
-    boolean isPersistent();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
-     * @see R.styleable#AndroidManifestProfileable
-     * @hide
-     */
-    boolean isProfileable();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
-     * @see R.styleable#AndroidManifestProfileable_shell
-     * @hide
-     */
-    boolean isProfileableByShell();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
-     * @see R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
-     * @hide
-     */
-    boolean isRequestLegacyExternalStorage();
-
-    /**
-     * @see PackageInfo#requiredForAllUsers
-     * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
-     * @hide
-     */
-    boolean isRequiredForAllUsers();
-
-    /**
-     * Whether the enabled settings of components in the application should be reset to the default,
-     * when the application's user data is cleared.
-     *
-     * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
-     * @hide
-     */
-    boolean isResetEnabledSettingsOnAppDataCleared();
-
-    /**
      * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
      * android.os.Build.VERSION_CODES#DONUT}.
      *
@@ -1432,13 +1456,6 @@
     boolean isResizeableActivityViaSdkVersion();
 
     /**
-     * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
-     * @see R.styleable#AndroidManifestApplication_restoreAnyVersion
-     * @hide
-     */
-    boolean isRestoreAnyVersion();
-
-    /**
      * True means that this package/app contains an SDK library.
      * @see R.styleable#AndroidManifestSdkLibrary
      * @hide
@@ -1459,76 +1476,6 @@
     boolean isStub();
 
     /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#GINGERBREAD}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
-     * @hide
-     */
-    boolean isSupportsExtraLargeScreens();
-
-    /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#DONUT}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
-     * @hide
-     */
-    boolean isSupportsLargeScreens();
-
-    /**
-     * If omitted from manifest, returns true.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
-     * @hide
-     */
-    boolean isSupportsNormalScreens();
-
-    /**
-     * @see ApplicationInfo#FLAG_SUPPORTS_RTL
-     * @see R.styleable#AndroidManifestApplication_supportsRtl
-     * @hide
-     */
-    boolean isSupportsRtl();
-
-    /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#DONUT}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
-     * @hide
-     */
-    boolean isSupportsSmallScreens();
-
-    /**
-     * @see ApplicationInfo#FLAG_TEST_ONLY
-     * @see R.styleable#AndroidManifestApplication_testOnly
-     * @hide
-     */
-    boolean isTestOnly();
-
-    /**
-     * The install time abi override to choose 32bit abi's when multiple abi's are present. This is
-     * only meaningful for multiarch applications. The use32bitAbi attribute is ignored if
-     * cpuAbiOverride is also set.
-     *
-     * @see R.attr#use32bitAbi
-     * @hide
-     */
-    boolean isUse32BitAbi();
-
-    /**
-     * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
-     * @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
-     * @hide
-     */
-    boolean isUsesCleartextTraffic();
-
-    /**
      * Set if the any of components are visible to instant applications.
      *
      * @see R.styleable#AndroidManifestActivity_visibleToInstantApps
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index ea791e1..3bd0e0d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -144,7 +144,7 @@
                                 | flag(ActivityInfo.FLAG_SYSTEM_USER_ONLY, R.styleable.AndroidManifestActivity_systemUserOnly, sa)));
 
             if (!receiver) {
-                activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isBaseHardwareAccelerated(), sa)
+                activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isHardwareAccelerated(), sa)
                                         | flag(ActivityInfo.FLAG_ALLOW_EMBEDDED, R.styleable.AndroidManifestActivity_allowEmbedded, sa)
                                         | flag(ActivityInfo.FLAG_ALWAYS_FOCUSABLE, R.styleable.AndroidManifestActivity_alwaysFocusable, sa)
                                         | flag(ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS, R.styleable.AndroidManifestActivity_autoRemoveFromRecents, sa)
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 16f5d16..12dfef4 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -155,7 +155,7 @@
 
     ParsingPackage setUiOptions(int uiOptions);
 
-    ParsingPackage setBaseHardwareAccelerated(boolean baseHardwareAccelerated);
+    ParsingPackage setHardwareAccelerated(boolean hardwareAccelerated);
 
     ParsingPackage setResizeableActivity(Boolean resizeable);
 
@@ -255,13 +255,13 @@
 
     ParsingPackage setBackupAgentName(String backupAgentName);
 
-    ParsingPackage setBanner(int banner);
+    ParsingPackage setBannerRes(int banner);
 
     ParsingPackage setCategory(int category);
 
     ParsingPackage setClassLoaderName(String classLoaderName);
 
-    ParsingPackage setClassName(String className);
+    ParsingPackage setApplicationClassName(String className);
 
     ParsingPackage setCompatibleWidthLimitDp(int compatibleWidthLimitDp);
 
@@ -281,9 +281,9 @@
 
     ParsingPackage setCrossProfile(boolean crossProfile);
 
-    ParsingPackage setFullBackupContent(int fullBackupContent);
+    ParsingPackage setFullBackupContentRes(int fullBackupContentRes);
 
-    ParsingPackage setDataExtractionRules(int dataExtractionRules);
+    ParsingPackage setDataExtractionRulesRes(int dataExtractionRulesRes);
 
     ParsingPackage setHasDomainUrls(boolean hasDomainUrls);
 
@@ -292,13 +292,13 @@
     ParsingPackage setInstallLocation(int installLocation);
 
     /** @see R#styleable.AndroidManifest_sharedUserMaxSdkVersion */
-    ParsingPackage setLeavingSharedUid(boolean leavingSharedUid);
+    ParsingPackage setLeavingSharedUser(boolean leavingSharedUser);
 
     ParsingPackage setLabelRes(int labelRes);
 
     ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
 
-    ParsingPackage setLogo(int logo);
+    ParsingPackage setLogoRes(int logo);
 
     ParsingPackage setManageSpaceActivityName(String manageSpaceActivityName);
 
@@ -336,13 +336,13 @@
 
     ParsingPackage setRoundIconRes(int roundIconRes);
 
-    ParsingPackage setSharedUserLabel(int sharedUserLabel);
+    ParsingPackage setSharedUserLabelRes(int sharedUserLabelRes);
 
     ParsingPackage setSigningDetails(@NonNull SigningDetails signingDetails);
 
     ParsingPackage setSplitClassLoaderName(int splitIndex, String classLoaderName);
 
-    ParsingPackage setStaticSharedLibVersion(long staticSharedLibVersion);
+    ParsingPackage setStaticSharedLibraryVersion(long staticSharedLibraryVersion);
 
     ParsingPackage setSupportsLargeScreens(int supportsLargeScreens);
 
@@ -354,7 +354,7 @@
 
     ParsingPackage setTargetSandboxVersion(int targetSandboxVersion);
 
-    ParsingPackage setTheme(int theme);
+    ParsingPackage setThemeRes(int theme);
 
     ParsingPackage setRequestForegroundServiceExemption(boolean requestForegroundServiceExemption);
 
@@ -512,7 +512,7 @@
 
     boolean isAnyDensity();
 
-    boolean isBaseHardwareAccelerated();
+    boolean isHardwareAccelerated();
 
     boolean isCantSaveState();
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index c6e1793..2a2640d 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1044,9 +1044,9 @@
         }
 
         return input.success(pkg
-                .setLeavingSharedUid(leaving)
+                .setLeavingSharedUser(leaving)
                 .setSharedUserId(str.intern())
-                .setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
+                .setSharedUserLabelRes(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
     }
 
     private static ParseResult<ParsingPackage> parseKeySets(ParseInput input,
@@ -1869,7 +1869,7 @@
                     return input.error("Empty class name in package " + packageName);
                 }
 
-                pkg.setClassName(outInfoName);
+                pkg.setApplicationClassName(outInfoName);
             }
 
             TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
@@ -1939,7 +1939,7 @@
                         fullBackupContent = v.data == 0 ? -1 : 0;
                     }
 
-                    pkg.setFullBackupContent(fullBackupContent);
+                    pkg.setFullBackupContentRes(fullBackupContent);
                 }
                 if (DEBUG_BACKUP) {
                     Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
@@ -2247,7 +2247,7 @@
                 .setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
                 // targetSdkVersion gated
                 .setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
-                .setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
+                .setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
                 .setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
                 .setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
                 // Ints Default 0
@@ -2258,14 +2258,14 @@
                 .setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
                 .setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
                 // Resource ID
-                .setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
+                .setBannerRes(resId(R.styleable.AndroidManifestApplication_banner, sa))
                 .setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
                 .setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
-                .setLogo(resId(R.styleable.AndroidManifestApplication_logo, sa))
+                .setLogoRes(resId(R.styleable.AndroidManifestApplication_logo, sa))
                 .setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
                 .setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
-                .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
-                .setDataExtractionRules(
+                .setThemeRes(resId(R.styleable.AndroidManifestApplication_theme, sa))
+                .setDataExtractionRulesRes(
                         resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
                 .setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
                 // Strings
@@ -2399,7 +2399,7 @@
             }
 
             return input.success(pkg.setStaticSharedLibraryName(lname.intern())
-                    .setStaticSharedLibVersion(
+                    .setStaticSharedLibraryVersion(
                             PackageInfo.composeLongVersionCode(versionMajor, version))
                     .setStaticSharedLibrary(true));
         } finally {
@@ -2694,7 +2694,7 @@
         // Build custom App Details activity info instead of parsing it from xml
         return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
                 pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
-                pkg.isBaseHardwareAccelerated()));
+                pkg.isHardwareAccelerated()));
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3fa5efc..a16e659 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -100,7 +100,6 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
 import com.android.server.pm.KnownPackages;
@@ -491,46 +490,13 @@
                         finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
                 if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                         || (finishWithRootActivity && r == rootR)) {
-                    ActivityRecord topActivity =
-                            r.getTask().getTopNonFinishingActivity();
-                    boolean passesAsmChecks = topActivity != null
-                            && topActivity.getUid() == r.getUid();
-                    if (!passesAsmChecks) {
-                        Slog.i(TAG, "Finishing task from background. r: " + r);
-                        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
-                                /* caller_uid */
-                                r.getUid(),
-                                /* caller_activity_class_name */
-                                r.info.name,
-                                /* target_task_top_activity_uid */
-                                topActivity == null ? -1 : topActivity.getUid(),
-                                /* target_task_top_activity_class_name */
-                                topActivity == null ? null : topActivity.info.name,
-                                /* target_task_is_different */
-                                false,
-                                /* target_activity_uid */
-                                -1,
-                                /* target_activity_class_name */
-                                null,
-                                /* target_intent_action */
-                                null,
-                                /* target_intent_flags */
-                                0,
-                                /* action */
-                                FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
-                                /* version */
-                                1,
-                                /* multi_window */
-                                false
-                        );
-                    }
                     // If requested, remove the task that is associated to this activity only if it
                     // was the root activity in the task. The result code and data is ignored
                     // because we don't support returning them across task boundaries. Also, to
                     // keep backwards compatibility we remove the task from recents when finishing
                     // task with root activity.
                     mTaskSupervisor.removeTask(tr, false /*killProcess*/,
-                            finishWithRootActivity, "finish-activity");
+                            finishWithRootActivity, "finish-activity", r.getUid(), r.info.name);
                     res = true;
                     // Explicitly dismissing the activity so reset its relaunch flag.
                     r.mRelaunchReason = RELAUNCH_REASON_NONE;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index c63bd52..ef126a9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.LocaleList;
@@ -622,10 +623,19 @@
         @Nullable
         public final LocaleList mLocales;
 
+        /**
+         * Gender for the application, null if app-specific grammatical gender is not set.
+         */
+        @Nullable
+        public final @Configuration.GrammaticalGender
+        Integer mGrammaticalGender;
+
         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-        public PackageConfig(Integer nightMode, LocaleList locales) {
+        public PackageConfig(Integer nightMode, LocaleList locales,
+                @Configuration.GrammaticalGender Integer grammaticalGender) {
             mNightMode = nightMode;
             mLocales = locales;
+            mGrammaticalGender = grammaticalGender;
         }
 
         /**
@@ -660,6 +670,13 @@
         PackageConfigurationUpdater setLocales(LocaleList locales);
 
         /**
+         * Sets the gender for the current application. This setting is persisted and will
+         * override the system configuration for this application.
+         */
+        PackageConfigurationUpdater setGrammaticalGender(
+                @Configuration.GrammaticalGender int gender);
+
+        /**
          * Commit changes.
          * @return true if the configuration changes were persisted,
          * false if there were no changes, or if erroneous inputs were provided, such as:
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1180df7..f4d76c2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -65,6 +65,7 @@
 import static android.provider.Settings.System.FONT_SCALE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
 
@@ -2843,7 +2844,7 @@
     }
 
     @Override
-    public boolean resizeTask(int taskId, Rect bounds, int resizeMode) {
+    public void resizeTask(int taskId, Rect bounds, int resizeMode) {
         enforceTaskPermission("resizeTask()");
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -2852,19 +2853,48 @@
                         MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
-                    return false;
+                    return;
                 }
                 if (!task.getWindowConfiguration().canResizeTask()) {
                     Slog.w(TAG, "resizeTask not allowed on task=" + task);
-                    return false;
+                    return;
                 }
 
                 // Reparent the task to the right root task if necessary
                 boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
 
-                // After reparenting (which only resizes the task to the root task bounds),
-                // resize the task to the actual bounds provided
-                return task.resize(bounds, resizeMode, preserveWindow);
+                if (!getTransitionController().isShellTransitionsEnabled()) {
+                    // After reparenting (which only resizes the task to the root task bounds),
+                    // resize the task to the actual bounds provided
+                    task.resize(bounds, resizeMode, preserveWindow);
+                    return;
+                }
+
+                final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
+                        getTransitionController(), mWindowManager.mSyncEngine);
+                if (mWindowManager.mSyncEngine.hasActiveSync()) {
+                    mWindowManager.mSyncEngine.queueSyncSet(
+                            () -> getTransitionController().moveToCollecting(transition),
+                            () -> {
+                                if (!task.getWindowConfiguration().canResizeTask()) {
+                                    Slog.w(TAG, "resizeTask not allowed on task=" + task);
+                                    transition.abort();
+                                    return;
+                                }
+                                getTransitionController().requestStartTransition(transition, task,
+                                        null /* remoteTransition */, null /* displayChange */);
+                                getTransitionController().collect(task);
+                                task.resize(bounds, resizeMode, preserveWindow);
+                                transition.setReady(task, true);
+                            });
+                } else {
+                    getTransitionController().moveToCollecting(transition);
+                    getTransitionController().requestStartTransition(transition, task,
+                            null /* remoteTransition */, null /* displayChange */);
+                    getTransitionController().collect(task);
+                    task.resize(bounds, resizeMode, preserveWindow);
+                    transition.setReady(task, true);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 42da2a5..3f885f3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -46,6 +46,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -98,6 +99,7 @@
 import android.app.servertransaction.LaunchActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -140,6 +142,7 @@
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService;
@@ -252,6 +255,7 @@
     private WindowManagerService mWindowManager;
 
     private AppOpsManager mAppOpsManager;
+    private VirtualDeviceManager mVirtualDeviceManager;
 
     /** Common synchronization logic used to save things to disks. */
     PersisterQueue mPersisterQueue;
@@ -895,12 +899,14 @@
 
                 final boolean isTransitionForward = r.isTransitionForward();
                 final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
+
+                final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
                 clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
                         mergedConfiguration.getGlobalConfiguration(),
-                        mergedConfiguration.getOverrideConfiguration(),
+                        mergedConfiguration.getOverrideConfiguration(), deviceId,
                         r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
                         proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
                         results, newIntents, r.takeOptions(), isTransitionForward,
@@ -1216,6 +1222,17 @@
         }
     }
 
+    int getDeviceIdForDisplayId(int displayId) {
+        if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY)  {
+            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+        }
+        if (mVirtualDeviceManager == null) {
+            mVirtualDeviceManager =
+                    mService.mContext.getSystemService(VirtualDeviceManager.class);
+        }
+        return mVirtualDeviceManager.getDeviceIdForDisplayId(displayId);
+    }
+
     private AppOpsManager getAppOpsManager() {
         if (mAppOpsManager == null) {
             mAppOpsManager = mService.mContext.getSystemService(AppOpsManager.class);
@@ -1590,11 +1607,11 @@
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
-            String reason) {
+            String reason, int callingUid) {
         final Task task =
                 mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
         if (task != null) {
-            removeTask(task, killProcess, removeFromRecents, reason);
+            removeTask(task, killProcess, removeFromRecents, reason, callingUid, null);
             return true;
         }
         Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
@@ -1602,10 +1619,52 @@
     }
 
     void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
+        removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, null);
+    }
+
+    void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason,
+            int callingUid, String callerActivityClassName) {
         if (task.mInRemoveTask) {
             // Prevent recursion.
             return;
         }
+        // We may have already checked that the callingUid has additional clearTask privileges, and
+        // cleared the calling identify. If so, we infer we do not need further restrictions here.
+        // TODO(b/263368846) Move to live with the rest of the ASM logic.
+        if (callingUid != SYSTEM_UID) {
+            ActivityRecord topActivity = task.getTopNonFinishingActivity();
+            boolean passesAsmChecks = topActivity != null
+                    && topActivity.getUid() == callingUid;
+            if (!passesAsmChecks) {
+                Slog.i(TAG, "Finishing task from background. t: " + task);
+                FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                        /* caller_uid */
+                        callingUid,
+                        /* caller_activity_class_name */
+                        callerActivityClassName,
+                        /* target_task_top_activity_uid */
+                        topActivity == null ? -1 : topActivity.getUid(),
+                        /* target_task_top_activity_class_name */
+                        topActivity == null ? null : topActivity.info.name,
+                        /* target_task_is_different */
+                        false,
+                        /* target_activity_uid */
+                        -1,
+                        /* target_activity_class_name */
+                        null,
+                        /* target_intent_action */
+                        null,
+                        /* target_intent_flags */
+                        0,
+                        /* action */
+                        FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+                        /* version */
+                        1,
+                        /* multi_window */
+                        false
+                );
+            }
+        }
         task.mTransitionController.requestCloseTransitionIfNeeded(task);
         task.mInRemoveTask = true;
         try {
@@ -1728,7 +1787,7 @@
             // Task was trimmed from the recent tasks list -- remove the active task record as well
             // since the user won't really be able to go back to it
             removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
-                    "recent-task-trimmed");
+                    "recent-task-trimmed", SYSTEM_UID);
         }
         task.removedFromRecents();
     }
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index b160af6a..7bd8c53 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -72,15 +72,16 @@
         checkCallerOrSystemOrRoot();
 
         synchronized (mService.mGlobalLock) {
-            final long origId = Binder.clearCallingIdentity();
+            int origCallingUid = Binder.getCallingUid();
+            final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 // We remove the task from recents to preserve backwards
                 if (!mService.mTaskSupervisor.removeTaskById(mTaskId, false,
-                        REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
+                        REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid)) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
             } finally {
-                Binder.restoreCallingIdentity(origId);
+                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 0c6cea8..58d4e82 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -536,16 +536,19 @@
      * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
      * @return true if any of the requested configuration has been updated.
      */
-    public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+    public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales,
+            @Configuration.GrammaticalGender Integer gender) {
         mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
         boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
                 nightMode);
         boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
                 locales);
-        if (newNightModeSet || newLocalesSet) {
+        boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
+                gender);
+        if (newNightModeSet || newLocalesSet || newGenderSet) {
             onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
         }
-        return newNightModeSet || newLocalesSet;
+        return newNightModeSet || newLocalesSet || newGenderSet;
     }
 
     /**
@@ -578,6 +581,21 @@
         return true;
     }
 
+    /**
+     * Overrides the gender to this ConfigurationContainer.
+     *
+     * @return true if the grammatical gender has been changed.
+     */
+    private boolean setOverrideGender(Configuration requestsTmpConfig,
+            @Configuration.GrammaticalGender int gender) {
+        if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
+            return false;
+        } else {
+            requestsTmpConfig.setGrammaticalGender(gender);
+            return true;
+        }
+    }
+
     public boolean isActivityTypeDream() {
         return getActivityType() == ACTIVITY_TYPE_DREAM;
     }
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 18a7d2e..23127ac 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import android.annotation.NonNull;
 import android.content.res.Configuration;
 import android.os.Environment;
@@ -165,7 +167,8 @@
             if (modifiedRecord != null) {
                 container.applyAppSpecificConfig(modifiedRecord.mNightMode,
                         LocaleOverlayHelper.combineLocalesIfOverlayExists(
-                        modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
+                        modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()),
+                        modifiedRecord.mGrammaticalGender);
             }
         }
     }
@@ -188,16 +191,19 @@
             }
             boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record);
             boolean isLocalesChanged = updateLocales(impl.getLocales(), record);
+            boolean isGenderChanged = updateGender(impl.getGrammaticalGender(), record);
 
             if ((record.mNightMode == null || record.isResetNightMode())
-                    && (record.mLocales == null || record.mLocales.isEmpty())) {
+                    && (record.mLocales == null || record.mLocales.isEmpty())
+                    && (record.mGrammaticalGender == null
+                            || record.mGrammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED)) {
                 // if all values default to system settings, we can remove the package.
                 removePackage(packageName, userId);
                 // if there was a pre-existing record for the package that was deleted,
                 // we return true (since it was successfully deleted), else false (since there was
                 // no change to the previous state).
                 return isRecordPresent;
-            } else if (!isNightModeChanged && !isLocalesChanged) {
+            } else if (!isNightModeChanged && !isLocalesChanged && !isGenderChanged) {
                 return false;
             } else {
                 final PackageConfigRecord pendingRecord =
@@ -211,7 +217,8 @@
                 }
 
                 if (!updateNightMode(record.mNightMode, writeRecord)
-                        && !updateLocales(record.mLocales, writeRecord)) {
+                        && !updateLocales(record.mLocales, writeRecord)
+                        && !updateGender(record.mGrammaticalGender, writeRecord)) {
                     return false;
                 }
 
@@ -240,6 +247,15 @@
         return true;
     }
 
+    private boolean updateGender(@Configuration.GrammaticalGender Integer requestedGender,
+            PackageConfigRecord record) {
+        if (requestedGender == null || requestedGender.equals(record.mGrammaticalGender)) {
+            return false;
+        }
+        record.mGrammaticalGender = requestedGender;
+        return true;
+    }
+
     @GuardedBy("mLock")
     void removeUser(int userId) {
         synchronized (mLock) {
@@ -305,7 +321,9 @@
                 return null;
             }
             return new ActivityTaskManagerInternal.PackageConfig(
-                    packageConfigRecord.mNightMode, packageConfigRecord.mLocales);
+                    packageConfigRecord.mNightMode,
+                    packageConfigRecord.mLocales,
+                    packageConfigRecord.mGrammaticalGender);
         }
     }
 
@@ -336,6 +354,8 @@
         final int mUserId;
         Integer mNightMode;
         LocaleList mLocales;
+        @Configuration.GrammaticalGender
+        Integer mGrammaticalGender;
 
         PackageConfigRecord(String name, int userId) {
             mName = name;
diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
index f3be66c..2cf8a4a 100644
--- a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
+++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.LocaleList;
 import android.util.ArraySet;
@@ -33,6 +34,8 @@
     private final Optional<Integer> mPid;
     private Integer mNightMode;
     private LocaleList mLocales;
+    private @Configuration.GrammaticalGender
+    int mGrammaticalGender;
     private String mPackageName;
     private int mUserId;
     private ActivityTaskManagerService mAtm;
@@ -68,6 +71,15 @@
     }
 
     @Override
+    public ActivityTaskManagerInternal.PackageConfigurationUpdater setGrammaticalGender(
+            @Configuration.GrammaticalGender int gender) {
+        synchronized (this) {
+            mGrammaticalGender = gender;
+        }
+        return this;
+    }
+
+    @Override
     public boolean commit() {
         synchronized (this) {
             synchronized (mAtm.mGlobalLock) {
@@ -112,12 +124,12 @@
         for (int i = processes.size() - 1; i >= 0; i--) {
             final WindowProcessController wpc = processes.valueAt(i);
             if (wpc.mInfo.packageName.equals(packageName)) {
-                wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+                wpc.applyAppSpecificConfig(mNightMode, localesOverride, mGrammaticalGender);
             }
             // Always inform individual activities about the update, since activities from other
             // packages may be sharing this process
             wpc.updateAppSpecificSettingsForAllActivitiesInPackage(packageName, mNightMode,
-                    localesOverride);
+                    localesOverride, mGrammaticalGender);
         }
     }
 
@@ -128,4 +140,9 @@
     LocaleList getLocales() {
         return mLocales;
     }
+
+    @Configuration.GrammaticalGender
+    Integer getGrammaticalGender() {
+        return mGrammaticalGender;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 13de4df..b83f423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6738,12 +6738,12 @@
             pw.print("  mDisplayFrozen="); pw.print(mDisplayFrozen);
                     pw.print(" windows="); pw.print(mWindowsFreezingScreen);
                     pw.print(" client="); pw.print(mClientFreezingScreen);
-                    pw.print(" apps="); pw.println(mAppsFreezingScreen);
+                    pw.print(" apps="); pw.print(mAppsFreezingScreen);
             final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
-            pw.print("  mRotation="); pw.println(defaultDisplayContent.getRotation());
+            pw.print("  mRotation="); pw.print(defaultDisplayContent.getRotation());
             pw.print("  mLastOrientation=");
                     pw.println(defaultDisplayContent.getLastOrientation());
-            pw.print("  mWaitingForConfig=");
+            pw.print(" waitingForConfig=");
                     pw.println(defaultDisplayContent.mWaitingForConfig);
 
             pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index dcd30bb..91452c6 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -52,6 +52,7 @@
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
 import android.app.servertransaction.ConfigurationChangeItem;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -206,6 +207,7 @@
     /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
     private volatile boolean mHasCachedConfiguration;
 
+    private int mTopActivityDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
     /**
      * Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
      * registered.
@@ -868,13 +870,13 @@
     // TODO(b/199277729): Consider whether we need to add special casing for edge cases like
     //  activity-embeddings etc.
     void updateAppSpecificSettingsForAllActivitiesInPackage(String packageName, Integer nightMode,
-            LocaleList localesOverride) {
+            LocaleList localesOverride, @Configuration.GrammaticalGender int gender) {
         for (int i = mActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = mActivities.get(i);
             // Activities from other packages could be sharing this process. Only propagate updates
             // to those activities that are part of the package whose app-specific settings changed
             if (packageName.equals(r.packageName)
-                    && r.applyAppSpecificConfig(nightMode, localesOverride)
+                    && r.applyAppSpecificConfig(nightMode, localesOverride, gender)
                     && r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
@@ -1381,8 +1383,16 @@
     @Override
     public void onConfigurationChanged(Configuration newGlobalConfig) {
         super.onConfigurationChanged(newGlobalConfig);
+
+        // If deviceId for the top-activity changed, schedule passing it to the app process.
+        boolean topActivityDeviceChanged = false;
+        int deviceId = getTopActivityDeviceId();
+        if (deviceId != mTopActivityDeviceId) {
+            topActivityDeviceChanged = true;
+        }
+
         final Configuration config = getConfiguration();
-        if (mLastReportedConfiguration.equals(config)) {
+        if (mLastReportedConfiguration.equals(config) & !topActivityDeviceChanged) {
             // Nothing changed.
             if (Build.IS_DEBUGGABLE && mHasImeService) {
                 // TODO (b/135719017): Temporary log for debugging IME service.
@@ -1396,7 +1406,34 @@
             mHasPendingConfigurationChange = true;
             return;
         }
-        dispatchConfiguration(config);
+
+        // TODO(b/263402938): Add tests that capture the deviceId dispatch to the client.
+        mTopActivityDeviceId = deviceId;
+        dispatchConfiguration(config, topActivityDeviceChanged ? mTopActivityDeviceId
+                : VirtualDeviceManager.DEVICE_ID_INVALID);
+    }
+
+    private int getTopActivityDeviceId() {
+        ActivityRecord topActivity = getTopNonFinishingActivity();
+        int updatedDeviceId = mTopActivityDeviceId;
+        if (topActivity != null && topActivity.mDisplayContent != null) {
+            updatedDeviceId = mAtm.mTaskSupervisor.getDeviceIdForDisplayId(
+                    topActivity.mDisplayContent.mDisplayId);
+        }
+        return updatedDeviceId;
+    }
+
+    @Nullable
+    private ActivityRecord getTopNonFinishingActivity() {
+        if (mActivities.isEmpty()) {
+            return null;
+        }
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            if (!mActivities.get(i).finishing) {
+                return mActivities.get(i);
+            }
+        }
+        return null;
     }
 
     @Override
@@ -1423,6 +1460,10 @@
     }
 
     void dispatchConfiguration(Configuration config) {
+        dispatchConfiguration(config, getTopActivityDeviceId());
+    }
+
+    void dispatchConfiguration(Configuration config, int deviceId) {
         mHasPendingConfigurationChange = false;
         if (mThread == null) {
             if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1449,10 +1490,16 @@
             }
         }
 
-        scheduleConfigurationChange(mThread, config);
+        scheduleConfigurationChange(mThread, config, deviceId);
     }
 
     private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
+        // By default send invalid deviceId as no-op signal so it's not updated on the client side.
+        scheduleConfigurationChange(thread, config, VirtualDeviceManager.DEVICE_ID_INVALID);
+    }
+
+    private void scheduleConfigurationChange(IApplicationThread thread, Configuration config,
+            int deviceId) {
         ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
                 config);
         if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1462,7 +1509,7 @@
         mHasCachedConfiguration = false;
         try {
             mAtm.getLifecycleManager().scheduleTransaction(thread,
-                    ConfigurationChangeItem.obtain(config));
+                    ConfigurationChangeItem.obtain(config, deviceId));
         } catch (Exception e) {
             Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
         }
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index d37f3bd..a1c5708 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -58,6 +58,7 @@
 jmethodID method_gnssAgcBuilderBuild;
 jmethodID method_gnssMeasurementsEventBuilderCtor;
 jmethodID method_gnssMeasurementsEventBuilderSetClock;
+jmethodID method_gnssMeasurementsEventBuilderSetFullTracking;
 jmethodID method_gnssMeasurementsEventBuilderSetMeasurements;
 jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls;
 jmethodID method_gnssMeasurementsEventBuilderBuild;
@@ -109,6 +110,10 @@
             env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls",
                              "([Landroid/location/GnssAutomaticGainControl;)"
                              "Landroid/location/GnssMeasurementsEvent$Builder;");
+    method_gnssMeasurementsEventBuilderSetFullTracking =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setFullTracking",
+                             "(Z)"
+                             "Landroid/location/GnssMeasurementsEvent$Builder;");
     method_gnssMeasurementsEventBuilderBuild =
             env->GetMethodID(class_gnssMeasurementsEventBuilder, "build",
                              "()Landroid/location/GnssMeasurementsEvent;");
@@ -228,7 +233,8 @@
 }
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
-                        jobjectArray measurementArray, jobjectArray gnssAgcArray) {
+                        jobjectArray measurementArray, jobjectArray gnssAgcArray,
+                        bool hasFullTracking, jboolean isFullTracking) {
     jobject gnssMeasurementsEventBuilderObject =
             env->NewObject(class_gnssMeasurementsEventBuilder,
                            method_gnssMeasurementsEventBuilderCtor);
@@ -240,6 +246,11 @@
     callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
                                    method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
                                    gnssAgcArray);
+    if (hasFullTracking) {
+        callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                       method_gnssMeasurementsEventBuilderSetFullTracking,
+                                       isFullTracking);
+    }
     jobject gnssMeasurementsEventObject =
             env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
                                   method_gnssMeasurementsEventBuilderBuild);
@@ -381,7 +392,14 @@
 
     jobjectArray gnssAgcArray = nullptr;
     gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
-    setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
+    if (this->getInterfaceVersion() >= 3) {
+        setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
+                           /*hasFullTracking=*/true, data.isFullTracking);
+    } else {
+        setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
+                           /*hasFullTracking=*/false,
+                           /*isFullTracking=*/JNI_FALSE);
+    }
 
     env->DeleteLocalRef(clock);
     env->DeleteLocalRef(measurementArray);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index c8f1803..fde56881 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -48,7 +48,8 @@
 void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz);
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
-                        jobjectArray measurementArray, jobjectArray gnssAgcArray);
+                        jobjectArray measurementArray, jobjectArray gnssAgcArray,
+                        bool hasFullTracking, jboolean isFullTracking);
 
 class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
 public:
@@ -140,7 +141,9 @@
     size_t count = getMeasurementCount(data);
     jobjectArray measurementArray =
             translateAllGnssMeasurements(env, data.measurements.data(), count);
-    setMeasurementData(env, mCallbacksObj, clock, measurementArray, nullptr);
+    setMeasurementData(env, mCallbacksObj, clock, measurementArray, /*gnssAgcArray=*/nullptr,
+                       /*hasFullTracking=*/false,
+                       /*isFullTracking=*/JNI_FALSE);
 
     env->DeleteLocalRef(clock);
     env->DeleteLocalRef(measurementArray);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bcb4ec9..5b9460a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.grammaticalinflection.GrammaticalInflectionService;
 import com.android.server.gpu.GpuService;
 import com.android.server.graphics.fonts.FontManagerService;
 import com.android.server.hdmi.HdmiControlService;
@@ -425,6 +426,8 @@
             "com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
     private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
             "com.android.server.adservices.AdServicesManagerService$Lifecycle";
+    private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
+            "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -1049,6 +1052,17 @@
     private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
         t.traceBegin("startBootstrapServices");
 
+        t.traceBegin("ArtModuleServiceInitializer");
+        // This needs to happen before DexUseManagerLocal init. We do it here to avoid colliding
+        // with a GC. ArtModuleServiceInitializer is a class from a separate dex file
+        // "service-art.jar", so referencing it involves the class linker. The class linker and the
+        // GC are mutually exclusive (b/263486535). Therefore, we do this here to force trigger the
+        // class linker earlier. If we did this later, especially after PackageManagerService init,
+        // the class linker would be consistently blocked by a GC because PackageManagerService
+        // allocates a lot of memory and almost certainly triggers a GC.
+        ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
+        t.traceEnd();
+
         // Start the watchdog as early as possible so we can crash the system server
         // if we deadlock during early boot
         t.traceBegin("StartWatchdog");
@@ -1235,8 +1249,6 @@
         t.traceBegin("DexUseManagerLocal");
         // DexUseManagerLocal needs to be loaded after PackageManagerLocal has been registered, but
         // before PackageManagerService starts processing binder calls to notifyDexLoad.
-        // DexUseManagerLocal may also call artd, so ensure ArtModuleServiceManager is instantiated.
-        ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
         LocalManagerRegistry.addManager(
                 DexUseManagerLocal.class, DexUseManagerLocal.createInstance());
         t.traceEnd();
@@ -1512,6 +1524,8 @@
 
             t.traceBegin("InstallSystemProviders");
             mActivityManagerService.getContentProviderHelper().installSystemProviders();
+            // Device configuration used to be part of System providers
+            mSystemServiceManager.startService(UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS);
             // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
             SQLiteCompatibilityWalFlags.reset();
             t.traceEnd();
@@ -1765,6 +1779,14 @@
         }
         t.traceEnd();
 
+        t.traceBegin("StartGrammarInflectionService");
+        try {
+            mSystemServiceManager.startService(GrammaticalInflectionService.class);
+        } catch (Throwable e) {
+            reportWtf("starting GrammarInflectionService service", e);
+        }
+        t.traceEnd();
+
         t.traceBegin("UpdatePackagesIfNeeded");
         try {
             Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 3727d66..b20e1dd 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -92,8 +92,11 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.PublicKey;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.PriorityQueue;
@@ -141,8 +144,7 @@
 
     /** make sure our initialized KeySetManagerService metadata matches packages.xml */
     @Test
-    public void testReadKeySetSettings()
-            throws ReflectiveOperationException, IllegalAccessException {
+    public void testReadKeySetSettings() throws Exception {
         /* write out files and read */
         writeOldFiles();
         Settings settings = makeSettings();
@@ -150,6 +152,29 @@
         verifyKeySetMetaData(settings);
     }
 
+    // Same as above but use the reserve copy.
+    @Test
+    public void testReadReserveCopyKeySetSettings() throws Exception {
+        /* write out files and read */
+        writeReserveCopyOldFiles();
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        verifyKeySetMetaData(settings);
+    }
+
+    // Same as above but packages.xml is malformed.
+    @Test
+    public void testReadMalformedPackagesXmlKeySetSettings() throws Exception {
+        // write out files
+        writeReserveCopyOldFiles();
+        // write corrupted packages.xml
+        writeCorruptedPackagesXml();
+
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        verifyKeySetMetaData(settings);
+    }
+
     /** read in data, write it out, and read it back in.  Verify same. */
     @Test
     public void testWriteKeySetSettings()
@@ -165,6 +190,39 @@
         verifyKeySetMetaData(settings);
     }
 
+    // Same as above, but corrupt the primary.xml in process.
+    @Test
+    public void testWriteCorruptReadKeySetSettings() throws Exception {
+        // write out files and read
+        writeOldFiles();
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+
+        // write out
+        settings.writeLPr(computer, /*sync=*/true);
+
+        File filesDir = InstrumentationRegistry.getContext().getFilesDir();
+        File packageXml = new File(filesDir, "system/packages.xml");
+        File packagesReserveCopyXml = new File(filesDir, "system/packages.xml.reservecopy");
+        // Primary.
+        assertTrue(packageXml.exists());
+        // Reserve copy.
+        assertTrue(packagesReserveCopyXml.exists());
+        // Temporary backup.
+        assertFalse(new File(filesDir, "packages-backup.xml").exists());
+
+        // compare two copies, make sure they are the same
+        assertTrue(Arrays.equals(Files.readAllBytes(Path.of(packageXml.getAbsolutePath())),
+                Files.readAllBytes(Path.of(packagesReserveCopyXml.getAbsolutePath()))));
+
+        // write corrupted packages.xml
+        writeCorruptedPackagesXml();
+
+        // read back in and verify the same
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        verifyKeySetMetaData(settings);
+    }
+
     @Test
     public void testSettingsReadOld() {
         // Write delegateshellthe package files and make sure they're parsed properly the first time
@@ -1572,9 +1630,19 @@
         }
     }
 
-    private void writePackagesXml() {
+    private void writeCorruptedPackagesXml() {
         writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.xml"),
                 ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                        + "<packages>"
+                        + "<last-platform-version internal=\"15\" external=\"0\" />"
+                        + "<permission-trees>"
+                        + "<item name=\"com.google.android.permtree\""
+                ).getBytes());
+    }
+
+    private void writePackagesXml(String fileName) {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), fileName),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<packages>"
                 + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />"
                 + "<permission-trees>"
@@ -1715,7 +1783,14 @@
 
     private void writeOldFiles() {
         deleteSystemFolder();
-        writePackagesXml();
+        writePackagesXml("system/packages.xml");
+        writeStoppedPackagesXml();
+        writePackagesList();
+    }
+
+    private void writeReserveCopyOldFiles() {
+        deleteSystemFolder();
+        writePackagesXml("system/packages.xml.reservecopy");
         writeStoppedPackagesXml();
         writePackagesList();
     }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index c8e2676..c6b7736 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -753,7 +753,7 @@
                 .setPVersionCode(pkg.getLongVersionCode())
                 .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
                 .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
-                .setSharedUserId(pkg.getSharedUserLabel())
+                .setSharedUserId(pkg.getSharedUserLabelRes())
                 .build();
     }
 
@@ -761,9 +761,9 @@
 
     public static void assertPackagesEqual(AndroidPackage a, AndroidPackage b) {
         assertEquals(a.getBaseRevisionCode(), b.getBaseRevisionCode());
-        assertEquals(a.isBaseHardwareAccelerated(), b.isBaseHardwareAccelerated());
+        assertEquals(a.isHardwareAccelerated(), b.isHardwareAccelerated());
         assertEquals(a.getLongVersionCode(), b.getLongVersionCode());
-        assertEquals(a.getSharedUserLabel(), b.getSharedUserLabel());
+        assertEquals(a.getSharedUserLabelRes(), b.getSharedUserLabelRes());
         assertEquals(a.getInstallLocation(), b.getInstallLocation());
         assertEquals(a.isCoreApp(), b.isCoreApp());
         assertEquals(a.isRequiredForAllUsers(), b.isRequiredForAllUsers());
@@ -1036,8 +1036,8 @@
         permission.setParsedPermissionGroup(new ParsedPermissionGroupImpl());
 
         ((ParsedPackage) pkg.setBaseRevisionCode(100)
-                .setBaseHardwareAccelerated(true)
-                .setSharedUserLabel(100)
+                .setHardwareAccelerated(true)
+                .setSharedUserLabelRes(100)
                 .setInstallLocation(100)
                 .setRequiredForAllUsers(true)
                 .asSplit(
@@ -1062,7 +1062,7 @@
                 .setSdkLibVersionMajor(42)
                 .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
                 .setStaticSharedLibraryName("foo23")
-                .setStaticSharedLibVersion(100)
+                .setStaticSharedLibraryVersion(100)
                 .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
                 .addLibraryName("foo10")
                 .addUsesLibrary("foo11")
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index b6f1b87..b5bd869 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -273,7 +273,7 @@
     public void installStaticSharedLibrary() throws Exception {
         final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
                 .setStaticSharedLibraryName("static.lib")
-                .setStaticSharedLibVersion(123L)
+                .setStaticSharedLibraryVersion(123L)
                 .hideAsParsed())
                 .setPackageName("static.lib.pkg.123")
                 .setVersionCodeMajor(1)
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 1c6ba33..9b3b8c35 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -33,11 +33,16 @@
         "junit",
         "kotlin-test",
         "kotlin-reflect",
+        "mockito-target-extended-minus-junit4",
         "services.core",
         "servicestests-utils",
         "servicestests-core-utils",
         "truth-prebuilt",
     ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
     platform_apis: true,
     test_suites: ["device-tests"],
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
index 2ef7a1f..81f6c82 100644
--- a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
@@ -18,6 +18,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.pm.test">
 
+    <!--required for using Mockito-->
+    <application android:debuggable="true" />
+
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.server.pm.test"
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index c439639..1619856 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -147,27 +147,27 @@
     )
 
     override val baseParams = listOf(
+        AndroidPackage::getApplicationClassName,
         AndroidPackage::getAppComponentFactory,
         AndroidPackage::getAutoRevokePermissions,
         AndroidPackage::getBackupAgentName,
-        AndroidPackage::getBanner,
+        AndroidPackage::getBannerRes,
         AndroidPackage::getBaseApkPath,
         AndroidPackage::getBaseRevisionCode,
         AndroidPackage::getCategory,
         AndroidPackage::getClassLoaderName,
-        AndroidPackage::getClassName,
         AndroidPackage::getCompatibleWidthLimitDp,
         AndroidPackage::getCompileSdkVersion,
         AndroidPackage::getCompileSdkVersionCodeName,
-        AndroidPackage::getDataExtractionRules,
+        AndroidPackage::getDataExtractionRulesRes,
         AndroidPackage::getDescriptionRes,
-        AndroidPackage::getFullBackupContent,
+        AndroidPackage::getFullBackupContentRes,
         AndroidPackage::getGwpAsanMode,
         AndroidPackage::getIconRes,
         AndroidPackage::getInstallLocation,
         AndroidPackage::getLabelRes,
         AndroidPackage::getLargestWidthLimitDp,
-        AndroidPackage::getLogo,
+        AndroidPackage::getLogoRes,
         AndroidPackage::getLocaleConfigRes,
         AndroidPackage::getManageSpaceActivityName,
         AndroidPackage::getMaxSdkVersion,
@@ -195,15 +195,15 @@
         PackageImpl::getSecondaryCpuAbi,
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
-        AndroidPackage::getSharedUserLabel,
+        AndroidPackage::getSharedUserLabelRes,
         AndroidPackage::getSdkLibraryName,
         AndroidPackage::getSdkLibVersionMajor,
         AndroidPackage::getStaticSharedLibraryName,
-        AndroidPackage::getStaticSharedLibVersion,
+        AndroidPackage::getStaticSharedLibraryVersion,
         AndroidPackage::getTargetSandboxVersion,
         AndroidPackage::getTargetSdkVersion,
         AndroidPackage::getTaskAffinity,
-        AndroidPackage::getTheme,
+        AndroidPackage::getThemeRes,
         AndroidPackage::getUiOptions,
         AndroidPackage::getUid,
         AndroidPackage::getVersionName,
@@ -215,7 +215,7 @@
         AndroidPackage::isAllowNativeHeapPointerTagging,
         AndroidPackage::isAllowTaskReparenting,
         AndroidPackage::isBackupInForeground,
-        AndroidPackage::isBaseHardwareAccelerated,
+        AndroidPackage::isHardwareAccelerated,
         AndroidPackage::isCantSaveState,
         AndroidPackage::isCoreApp,
         AndroidPackage::isCrossProfile,
@@ -260,7 +260,7 @@
         AndroidPackage::isUsesNonSdkApi,
         AndroidPackage::isVisibleToInstantApps,
         AndroidPackage::isVmSafeMode,
-        AndroidPackage::isLeavingSharedUid,
+        AndroidPackage::isLeavingSharedUser,
         AndroidPackage::isResetEnabledSettingsOnAppDataCleared,
         AndroidPackage::getMaxAspectRatio,
         AndroidPackage::getMinAspectRatio,
@@ -293,7 +293,7 @@
         adder(AndroidPackage::getUsesOptionalLibraries, "testUsesOptionalLibrary"),
         adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
         getSetByValue(
-            AndroidPackage::areAttributionsUserVisible,
+            AndroidPackage::isAttributionsUserVisible,
             PackageImpl::setAttributionsAreUserVisible,
             true
         ),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt
new file mode 100644
index 0000000..52fc91d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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.test.uninstall
+
+import android.app.PackageDeleteObserver
+import android.content.Intent
+import android.content.pm.IPackageDeleteObserver2
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.UninstallCompleteCallback
+import android.os.Parcel
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+class UninstallCompleteCallbackTest {
+
+    val PACKAGE_NAME: String = "com.example.package"
+    val ERROR_MSG: String = "no error"
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    lateinit var mockAdapter: PackageDeleteObserver
+
+    val mockBinder: IPackageDeleteObserver2.Stub = object : IPackageDeleteObserver2.Stub() {
+        override fun onUserActionRequired(intent: Intent) {
+            mockAdapter.onUserActionRequired(intent)
+        }
+        override fun onPackageDeleted(basePackageName: String, returnCode: Int, msg: String) {
+            mockAdapter.onPackageDeleted(basePackageName, returnCode, msg)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+    }
+
+    @Test
+    fun testCallDelegation () {
+        doReturn(mockBinder).`when`(mockAdapter).binder
+
+        val callback = UninstallCompleteCallback(mockAdapter.binder.asBinder())
+        callback.onUninstallComplete(PACKAGE_NAME, PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+
+        verify(mockAdapter, times(1)).onPackageDeleted(PACKAGE_NAME,
+            PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+    }
+
+    @Test
+    fun testClassIsParcelable() {
+        doReturn(mockBinder).`when`(mockAdapter).binder
+
+        val callback = UninstallCompleteCallback(mockAdapter.binder.asBinder())
+
+        val parcel = Parcel.obtain()
+        callback.writeToParcel(parcel, callback.describeContents())
+        parcel.setDataPosition(0)
+
+        val callbackFromParcel = UninstallCompleteCallback.CREATOR.createFromParcel(parcel)
+
+        callbackFromParcel.onUninstallComplete(PACKAGE_NAME, PackageManager.DELETE_SUCCEEDED,
+                ERROR_MSG)
+
+        verify(mockAdapter, times(1)).onPackageDeleted(PACKAGE_NAME,
+            PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8d78cd6..f56b0b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -197,7 +197,7 @@
 
     private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
             BroadcastRecord record, int recordIndex, long enqueueTime) {
-        queue.enqueueOrReplaceBroadcast(record, recordIndex);
+        queue.enqueueOrReplaceBroadcast(record, recordIndex, null /* replacedBroadcastConsumer */);
         record.enqueueTime = enqueueTime;
     }
 
@@ -327,7 +327,7 @@
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
                 List.of(makeMockRegisteredReceiver()));
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
 
         queue.setProcessCached(false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -349,12 +349,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0);
+        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, null /* replacedBroadcastConsumer */);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -385,7 +385,7 @@
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
                 List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
                         withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, null /* replacedBroadcastConsumer */);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -408,7 +408,7 @@
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
                 List.of(makeMockRegisteredReceiver()));
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -434,11 +434,11 @@
                 new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
                 List.of(makeMockRegisteredReceiver()));
 
-        queue.enqueueOrReplaceBroadcast(lazyRecord, 0);
+        queue.enqueueOrReplaceBroadcast(lazyRecord, 0, null /* replacedBroadcastConsumer */);
         assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
 
-        queue.enqueueOrReplaceBroadcast(testRecord, 0);
+        queue.enqueueOrReplaceBroadcast(testRecord, 0, null /* replacedBroadcastConsumer */);
         assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
     }
@@ -507,20 +507,26 @@
 
         queue.enqueueOrReplaceBroadcast(
                 makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
+                null /* replacedBroadcastConsumer */);
         queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0,
+                null /* replacedBroadcastConsumer */);
         queue.enqueueOrReplaceBroadcast(
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
+                null /* replacedBroadcastConsumer */);
         queue.enqueueOrReplaceBroadcast(
                 makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
+                null /* replacedBroadcastConsumer */);
         queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0,
+                null /* replacedBroadcastConsumer */);
         queue.enqueueOrReplaceBroadcast(
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
+                null /* replacedBroadcastConsumer */);
 
         queue.makeActiveNextPending();
         assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index d79c4d8..6800d52 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -154,6 +154,7 @@
 
     private ActivityManagerService mAms;
     private BroadcastQueue mQueue;
+    BroadcastConstants mConstants;
 
     /**
      * Desired behavior of the next
@@ -277,10 +278,9 @@
         }).when(mAms).getProcessRecordLocked(any(), anyInt());
         doNothing().when(mAms).appNotResponding(any(), any());
 
-        final BroadcastConstants constants = new BroadcastConstants(
-                Settings.Global.BROADCAST_FG_CONSTANTS);
-        constants.TIMEOUT = 100;
-        constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
+        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+        mConstants.TIMEOUT = 100;
+        mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
         final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
             public boolean shouldSkip(BroadcastRecord r, Object o) {
                 // Ignored
@@ -291,7 +291,7 @@
                 return null;
             }
         };
-        final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {
+        final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
             public void addBroadcastToHistoryLocked(BroadcastRecord original) {
                 // Ignored
             }
@@ -299,13 +299,13 @@
 
         if (mImpl == Impl.DEFAULT) {
             var q = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
-                    constants, emptySkipPolicy, emptyHistory, false,
+                    mConstants, emptySkipPolicy, emptyHistory, false,
                     ProcessList.SCHED_GROUP_DEFAULT);
             q.mReceiverBatch.mDeepReceiverCopy = true;
             mQueue = q;
         } else if (mImpl == Impl.MODERN) {
             var q = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-                    constants, constants, emptySkipPolicy, emptyHistory);
+                    mConstants, mConstants, emptySkipPolicy, emptyHistory);
             q.mReceiverBatch.mDeepReceiverCopy = true;
             mQueue = q;
         } else {
@@ -1703,6 +1703,28 @@
     }
 
     @Test
+    public void testReplacePending_withPrioritizedBroadcasts() throws Exception {
+        mConstants.MAX_RUNNING_ACTIVE_BROADCASTS = 1;
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT)
+                .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+        final List receivers = List.of(
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 100),
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_RED), 50),
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_YELLOW), 10),
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE), 0));
+
+        // Enqueue the broadcast a few times and verify that broadcast queues are not stuck
+        // and are emptied eventually.
+        for (int i = 0; i < 6; ++i) {
+            enqueueBroadcast(makeBroadcastRecord(userPresent, callerApp, receivers));
+        }
+        waitForIdle();
+    }
+
+    @Test
     public void testIdleAndBarrier() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index cd2f205..3480af6 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -25,11 +28,11 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 import android.content.Context;
@@ -37,6 +40,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.Presubmit;
+import android.util.FeatureFlagUtils;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -47,15 +51,21 @@
 import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
 
 import com.google.common.collect.ImmutableSet;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.function.IntConsumer;
 
 @Presubmit
@@ -63,6 +73,7 @@
 public class UserBackupManagerServiceTest {
     private static final String TEST_PACKAGE = "package1";
     private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
+    private static final String TEST_TRANSPORT = "transport";
     private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
 
     @Mock Context mContext;
@@ -70,21 +81,38 @@
     @Mock IBackupObserver mBackupObserver;
     @Mock PackageManager mPackageManager;
     @Mock TransportConnection mTransportConnection;
+    @Mock TransportManager mTransportManager;
     @Mock BackupTransportClient mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
     @Mock LifecycleOperationStorage mOperationStorage;
 
+    private MockitoSession mSession;
     private TestBackupService mService;
 
     @Before
     public void setUp() throws Exception {
+        mSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(BackupManagerMonitorUtils.class)
+                .mockStatic(FeatureFlagUtils.class)
+                // TODO(b/263239775): Remove unnecessary stubbing.
+                .strictness(Strictness.LENIENT)
+                .startMocking();
         MockitoAnnotations.initMocks(this);
 
-        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage);
+        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
+                mTransportManager);
         mService.setEnabled(true);
         mService.setSetupComplete(true);
     }
 
+    @After
+    public void tearDown() {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
     @Test
     public void initializeBackupEnableState_doesntWriteStateToDisk() {
         mService.initializeBackupEnableState();
@@ -201,6 +229,26 @@
                 .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class));
     }
 
+    @Test
+    public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception {
+        PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE);
+        when(mPackageManager.getPackageInfoAsUser(anyString(),
+                any(PackageManager.PackageInfoFlags.class), anyInt())).thenReturn(packageInfo);
+        when(mTransportManager.getCurrentTransportName()).thenReturn(TEST_TRANSPORT);
+        when(mTransportManager.getTransportClientOrThrow(eq(TEST_TRANSPORT), anyString()))
+                .thenReturn(mTransportConnection);
+        when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport);
+        when(mBackupTransport.getBackupManagerMonitor()).thenReturn(mBackupManagerMonitor);
+
+
+        List<DataTypeResult> results = Arrays.asList(new DataTypeResult(/* dataType */ "type_1"),
+                new DataTypeResult(/* dataType */ "type_2"));
+        mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
+
+        verify(() -> BackupManagerMonitorUtils.sendAgentLoggingResults(
+                eq(mBackupManagerMonitor), eq(packageInfo), eq(results)));
+    }
+
     private static PackageInfo getPackageInfo(String packageName) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -215,8 +263,8 @@
         private volatile Thread mWorkerThread = null;
 
         TestBackupService(Context context, PackageManager packageManager,
-                LifecycleOperationStorage operationStorage) {
-            super(context, packageManager, operationStorage);
+                LifecycleOperationStorage operationStorage, TransportManager transportManager) {
+            super(context, packageManager, operationStorage, transportManager);
         }
 
         @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index c4ff4f0..a8b8f91 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -254,6 +254,12 @@
 
         ConnectivityController connectivityController = mService.getConnectivityController();
         spyOn(connectivityController);
+        mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 15 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+        mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
 
         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -268,7 +274,7 @@
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDef));
         grantRunLongJobsPermission(false); // Without permission
-        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDT));
         grantRunLongJobsPermission(true); // With permission
         doReturn(ConnectivityController.UNKNOWN_TIME)
@@ -288,12 +294,16 @@
         assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDT));
         // UserInitiated
+        grantRunLongJobsPermission(false);
+        // Permission isn't granted, so it should just be treated as a regular data transfer job.
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        // Permission isn't granted, so it should just be treated as a regular job.
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUI));
+        grantRunLongJobsPermission(true); // With permission
         assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUI));
-        grantRunLongJobsPermission(false);
-        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
-        grantRunLongJobsPermission(true); // With permission
         doReturn(ConnectivityController.UNKNOWN_TIME)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
         assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 28c78b2..cffd027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -391,7 +391,7 @@
             libraries?.forEach { pkg.addLibraryName(it) }
             staticLibrary?.let {
                 pkg.setStaticSharedLibraryName(it)
-                pkg.setStaticSharedLibVersion(staticLibraryVersion)
+                pkg.setStaticSharedLibraryVersion(staticLibraryVersion)
                 pkg.setStaticSharedLibrary(true)
             }
             usesLibraries?.forEach { pkg.addUsesLibrary(it) }
@@ -435,7 +435,7 @@
             libraries?.forEach { addLibraryName(it) }
             staticLibrary?.let {
                 setStaticSharedLibraryName(it)
-                setStaticSharedLibVersion(staticLibraryVersion)
+                setStaticSharedLibraryVersion(staticLibraryVersion)
                 setStaticSharedLibrary(true)
             }
             usesLibraries?.forEach { addUsesLibrary(it) }
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index ee9d59b..08a0878 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -75,7 +75,13 @@
     }
 
     @Override
-    public int removeDevicesRoleForStrategy(int strategy, int role) {
+    public int removeDevicesRoleForStrategy(int strategy, int role,
+            @NonNull List<AudioDeviceAttributes> devices) {
+        return AudioSystem.AUDIO_STATUS_OK;
+    }
+
+    @Override
+    public int clearDevicesRoleForStrategy(int strategy, int role) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
index cadc890..87ade96 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
@@ -172,11 +172,30 @@
                 .when(agent)
                 .getLoggerResults(any());
 
-        IBackupManagerMonitor result =
+        IBackupManagerMonitor monitor =
                 BackupManagerMonitorUtils.monitorAgentLoggingResults(
                         mMonitorMock, packageInfo, agent);
 
-        assertThat(result).isEqualTo(mMonitorMock);
+        assertCorrectBundleSentToMonitor(monitor);
+    }
+
+    @Test
+    public void sendAgentLoggingResults_fillsBundleCorrectly() throws Exception {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = "test.package";
+        List<BackupRestoreEventLogger.DataTypeResult> loggingResults = new ArrayList<>();
+        loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult"));
+
+        IBackupManagerMonitor monitor = BackupManagerMonitorUtils.sendAgentLoggingResults(
+                mMonitorMock, packageInfo, loggingResults);
+
+        assertCorrectBundleSentToMonitor(monitor);
+    }
+
+    private void assertCorrectBundleSentToMonitor(IBackupManagerMonitor monitor) throws Exception {
+
+
+        assertThat(monitor).isEqualTo(mMonitorMock);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mMonitorMock).onEvent(bundleCaptor.capture());
         Bundle eventBundle = bundleCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ac880ce..89eaa2c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -35,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
-import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -97,8 +96,9 @@
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
@@ -107,6 +107,7 @@
 import com.google.android.collect.Sets;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -177,17 +178,14 @@
                     .setAssociatedDisplayId(DISPLAY_ID_1)
                     .build();
     private static final VirtualTouchscreenConfig TOUCHSCREEN_CONFIG =
-            new VirtualTouchscreenConfig.Builder()
+            new VirtualTouchscreenConfig.Builder(WIDTH, HEIGHT)
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
                     .setAssociatedDisplayId(DISPLAY_ID_1)
-                    .setWidthInPixels(WIDTH)
-                    .setHeightInPixels(HEIGHT)
                     .build();
     private static final VirtualNavigationTouchpadConfig NAVIGATION_TOUCHPAD_CONFIG =
-            new VirtualNavigationTouchpadConfig.Builder(
-                    /* touchpadHeight= */ HEIGHT, /* touchpadWidth= */ WIDTH)
+            new VirtualNavigationTouchpadConfig.Builder(WIDTH, HEIGHT)
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
@@ -195,6 +193,11 @@
                     .build();
     private static final String TEST_SITE = "http://test";
 
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            Manifest.permission.CREATE_VIRTUAL_DEVICE);
+
     private Context mContext;
     private InputManagerMockHelper mInputManagerMockHelper;
     private VirtualDeviceImpl mDeviceImpl;
@@ -304,10 +307,9 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mContext = Mockito.spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
-        doNothing().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManagerMock);
 
@@ -726,48 +728,28 @@
 
     @Test
     public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        final VirtualTouchscreenConfig zeroConfig =
-                new VirtualTouchscreenConfig.Builder()
-                        .setVendorId(VENDOR_ID)
-                        .setProductId(PRODUCT_ID)
-                        .setInputDeviceName(DEVICE_NAME)
-                        .setAssociatedDisplayId(DISPLAY_ID_1)
-                        .setWidthInPixels(0)
-                        .setHeightInPixels(0)
-                        .build();
         assertThrows(IllegalArgumentException.class,
-                () -> mDeviceImpl.createVirtualTouchscreen(zeroConfig, BINDER));
+                () -> new VirtualTouchscreenConfig.Builder(
+                        /* touchscrenWidth= */ 0, /* touchscreenHeight= */ 0));
     }
 
     @Test
     public void createVirtualTouchscreen_negativeDisplayDimension_failsIllegalArgumentException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        final VirtualTouchscreenConfig negativeConfig =
-                new VirtualTouchscreenConfig.Builder()
-                        .setVendorId(VENDOR_ID)
-                        .setProductId(PRODUCT_ID)
-                        .setInputDeviceName(DEVICE_NAME)
-                        .setAssociatedDisplayId(DISPLAY_ID_1)
-                        .setWidthInPixels(-100)
-                        .setHeightInPixels(-100)
-                        .build();
         assertThrows(IllegalArgumentException.class,
-                () -> mDeviceImpl.createVirtualTouchscreen(negativeConfig, BINDER));
-
+                () -> new VirtualTouchscreenConfig.Builder(
+                        /* touchscrenWidth= */ -100, /* touchscreenHeight= */ -100));
     }
 
     @Test
     public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         VirtualTouchscreenConfig positiveConfig =
-                new VirtualTouchscreenConfig.Builder()
+                new VirtualTouchscreenConfig.Builder(
+                        /* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
                         .setVendorId(VENDOR_ID)
                         .setProductId(PRODUCT_ID)
                         .setInputDeviceName(DEVICE_NAME)
                         .setAssociatedDisplayId(DISPLAY_ID_1)
-                        .setWidthInPixels(600)
-                        .setHeightInPixels(800)
                         .build();
         mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
         assertWithMessage(
@@ -784,36 +766,16 @@
 
     @Test
     public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         assertThrows(IllegalArgumentException.class,
-                () -> {
-                    final VirtualNavigationTouchpadConfig zeroConfig =
-                            new VirtualNavigationTouchpadConfig.Builder(
-                                    /* touchpadHeight= */ 0, /* touchpadWidth= */ 0)
-                                    .setVendorId(VENDOR_ID)
-                                    .setProductId(PRODUCT_ID)
-                                    .setInputDeviceName(DEVICE_NAME)
-                                    .setAssociatedDisplayId(DISPLAY_ID_1)
-                                    .build();
-                    mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
-                });
+                () -> new VirtualNavigationTouchpadConfig.Builder(
+                        /* touchpadHeight= */ 0, /* touchpadWidth= */ 0));
     }
 
     @Test
     public void createVirtualNavigationTouchpad_negativeDisplayDimension_failsWithException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         assertThrows(IllegalArgumentException.class,
-                () -> {
-                    final VirtualNavigationTouchpadConfig negativeConfig =
-                            new VirtualNavigationTouchpadConfig.Builder(
-                                    /* touchpadHeight= */ -50, /* touchpadWidth= */ 50)
-                                    .setVendorId(VENDOR_ID)
-                                    .setProductId(PRODUCT_ID)
-                                    .setInputDeviceName(DEVICE_NAME)
-                                    .setAssociatedDisplayId(DISPLAY_ID_1)
-                                    .build();
-                    mDeviceImpl.createVirtualNavigationTouchpad(negativeConfig, BINDER);
-                });
+                () -> new VirtualNavigationTouchpadConfig.Builder(
+                        /* touchpadHeight= */ -50, /* touchpadWidth= */ 50));
     }
 
     @Test
@@ -844,76 +806,76 @@
     @Test
     public void createVirtualDpad_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualMouse_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualTouchscreen_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
-                        BINDER));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+                            BINDER));
+        }
     }
 
     @Test
     public void createVirtualSensor_noPermission_failsSecurityException() {
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualSensor(
-                        BINDER,
-                        new VirtualSensorConfig.Builder(
-                                Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(
+                    SecurityException.class,
+                    () -> mDeviceImpl.createVirtualSensor(
+                            BINDER,
+                            new VirtualSensorConfig.Builder(
+                                    Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+        }
     }
 
     @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.onAudioSessionStarting(
-                        DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.onAudioSessionStarting(
+                            DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
+        }
     }
 
     @Test
     public void onAudioSessionEnded_noPermission_failsSecurityException() {
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
+        }
     }
 
     @Test
@@ -1593,4 +1555,18 @@
         mVdms.addVirtualDevice(virtualDeviceImpl);
         return virtualDeviceImpl;
     }
+
+    /** Helper class to drop permissions temporarily and restore them at the end of a test. */
+    static final class DropShellPermissionsTemporarily implements AutoCloseable {
+        DropShellPermissionsTemporarily() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+
+        @Override
+        public void close() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index bd4058a..69a0b87 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -73,6 +74,7 @@
 import android.test.mock.MockContentResolver;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.Display;
 import android.view.SurfaceControl.RefreshRateRange;
 import android.view.SurfaceControl.RefreshRateRanges;
@@ -102,6 +104,7 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -2293,6 +2296,61 @@
                 new int[]{20});
     }
 
+    @Test
+    public void testSensorReloadOnDeviceSwitch() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
+        when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
+        director.start(sensorManager);
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensorOne),
+                        anyInt(),
+                        any(Handler.class));
+
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
+        when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+
+        Resources resMock = mock(Resources.class);
+        when(resMock.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+                .thenReturn(3);
+        ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+        doAnswer((Answer<Void>) invocation -> {
+            valueArgumentCaptor.getValue().type = 4;
+            valueArgumentCaptor.getValue().data = 13;
+            return null;
+        }).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
+        when(mContext.getResources()).thenReturn(resMock);
+
+        director.defaultDisplayDeviceUpdated(ddcMock);
+
+        verify(sensorManager).unregisterListener(any(SensorEventListener.class));
+        verify(sensorManager).registerListener(any(SensorEventListener.class),
+                eq(lightSensorTwo), anyInt(), any(Handler.class));
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index d90f53a..1c44da1 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,8 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -15,6 +17,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobInfo.Builder;
+import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
@@ -31,6 +34,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.job.JobStore.JobSet;
 import com.android.server.job.controllers.JobStatus;
@@ -43,6 +47,10 @@
 import java.io.File;
 import java.time.Clock;
 import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Test reading and writing correctly from file.
@@ -690,20 +698,46 @@
                 taskStatus.getJob().isRequireBatteryNotLow());
     }
 
+    @Test
+    public void testJobWorkItems() throws Exception {
+        JobWorkItem item1 = new JobWorkItem.Builder().build();
+        item1.bumpDeliveryCount();
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean("test", true);
+        JobWorkItem item2 = new JobWorkItem.Builder().setExtras(bundle).build();
+        item2.bumpDeliveryCount();
+        JobWorkItem item3 = new JobWorkItem.Builder().setEstimatedNetworkBytes(1, 2).build();
+        JobWorkItem item4 = new JobWorkItem.Builder().setMinimumNetworkChunkBytes(3).build();
+        JobWorkItem item5 = new JobWorkItem.Builder().build();
+
+        JobInfo jobInfo = new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .build();
+        JobStatus jobStatus =
+                JobStatus.createFromJobInfo(jobInfo, SOME_UID, null, -1, null, null);
+        jobStatus.executingWork = new ArrayList<>(List.of(item1, item2));
+        jobStatus.pendingWork = new ArrayList<>(List.of(item3, item4, item5));
+        assertPersistedEquals(jobStatus);
+    }
+
     /**
      * Helper function to kick a {@link JobInfo} through a persistence cycle and
      * assert that it's unchanged.
      */
     private void assertPersistedEquals(JobInfo firstInfo) throws Exception {
+        assertPersistedEquals(
+                JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null));
+    }
+
+    private void assertPersistedEquals(JobStatus original) throws Exception {
         mTaskStoreUnderTest.clear();
-        JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null);
-        mTaskStoreUnderTest.add(first);
+        mTaskStoreUnderTest.add(original);
         waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
-        assertJobsEqual(first, second);
+        assertJobsEqual(original, second);
     }
 
     /**
@@ -729,6 +763,59 @@
                 expected.getEarliestRunTime(), actual.getEarliestRunTime());
         compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
                 expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
+
+        assertEquals(expected.hasWorkLocked(), actual.hasWorkLocked());
+        if (expected.hasWorkLocked()) {
+            List<JobWorkItem> allWork = new ArrayList<>();
+            if (expected.executingWork != null) {
+                allWork.addAll(expected.executingWork);
+            }
+            if (expected.pendingWork != null) {
+                allWork.addAll(expected.pendingWork);
+            }
+            // All work for freshly loaded Job will be pending.
+            assertNotNull(actual.pendingWork);
+            assertTrue(ArrayUtils.isEmpty(actual.executingWork));
+            assertEquals(allWork.size(), actual.pendingWork.size());
+            for (int i = 0; i < allWork.size(); ++i) {
+                JobWorkItem expectedItem = allWork.get(i);
+                JobWorkItem actualItem = actual.pendingWork.get(i);
+                assertJobWorkItemsEqual(expectedItem, actualItem);
+            }
+        }
+    }
+
+    private void assertJobWorkItemsEqual(JobWorkItem expected, JobWorkItem actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return;
+        }
+        assertNotNull(actual);
+        assertEquals(expected.getDeliveryCount(), actual.getDeliveryCount());
+        assertEquals(expected.getEstimatedNetworkDownloadBytes(),
+                actual.getEstimatedNetworkDownloadBytes());
+        assertEquals(expected.getEstimatedNetworkUploadBytes(),
+                actual.getEstimatedNetworkUploadBytes());
+        assertEquals(expected.getMinimumNetworkChunkBytes(), actual.getMinimumNetworkChunkBytes());
+        if (expected.getIntent() == null) {
+            assertNull(actual.getIntent());
+        } else {
+            // filterEquals() just so happens to check almost everything that is persisted to disk.
+            assertTrue(expected.getIntent().filterEquals(actual.getIntent()));
+            assertEquals(expected.getIntent().getFlags(), actual.getIntent().getFlags());
+        }
+        assertEquals(expected.getGrants(), actual.getGrants());
+        PersistableBundle expectedExtras = expected.getExtras();
+        PersistableBundle actualExtras = actual.getExtras();
+        if (expectedExtras == null) {
+            assertNull(actualExtras);
+        } else {
+            assertEquals(expectedExtras.size(), actualExtras.size());
+            Set<String> keys = expectedExtras.keySet();
+            for (String key : keys) {
+                assertTrue(Objects.equals(expectedExtras.get(key), actualExtras.get(key)));
+            }
+        }
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
index 6cdae53..56df9d9 100644
--- a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
+++ b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locales;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import android.annotation.Nullable;
 import android.os.LocaleList;
 
@@ -29,6 +31,8 @@
 
     FakePackageConfigurationUpdater() {}
 
+    private int mGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
     LocaleList mLocales = null;
 
     @Override
@@ -43,6 +47,12 @@
     }
 
     @Override
+    public PackageConfigurationUpdater setGrammaticalGender(int gender) {
+        mGender = gender;
+        return this;
+    }
+
+    @Override
     public boolean commit() {
         return mLocales != null;
     }
@@ -56,4 +66,10 @@
         return mLocales;
     }
 
+    /**
+     * Returns the gender that were stored during the test run.
+     */
+    int getGender() {
+        return mGender;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 79ed7d1..065aec5 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locales;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -234,7 +236,8 @@
             throws Exception {
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
         String imPkgName = getCurrentInputMethodPackageName();
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
@@ -274,7 +277,8 @@
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
-        doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
+        doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
 
         LocaleList locales = mLocaleManagerService.getApplicationLocales(
@@ -288,7 +292,8 @@
             throws Exception {
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales =
@@ -303,7 +308,8 @@
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales =
@@ -319,7 +325,8 @@
                 .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
                 .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales =
@@ -334,7 +341,8 @@
             throws Exception {
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
         String imPkgName = getCurrentInputMethodPackageName();
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index cbf555e..494796e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locales;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
@@ -168,7 +170,8 @@
             /* isUpdatedSystemApp = */ true))
             .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
         doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
-                DEFAULT_LOCALES)).when(mMockActivityTaskManager)
+                        DEFAULT_LOCALES, GRAMMATICAL_GENDER_NOT_SPECIFIED))
+                .when(mMockActivityTaskManager)
                 .getApplicationConfig(anyString(), anyInt());
 
         mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
@@ -186,7 +189,8 @@
             /* isUpdatedSystemApp = */ true))
             .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
         doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
-                DEFAULT_LOCALES)).when(mMockActivityTaskManager)
+                        DEFAULT_LOCALES, GRAMMATICAL_GENDER_NOT_SPECIFIED))
+                .when(mMockActivityTaskManager)
                 .getApplicationConfig(anyString(), anyInt());
 
         // first update
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index b16ca8b..b4a294d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -19,6 +19,7 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
+import android.os.Parcel;
 import android.service.notification.ZenPolicy;
 import android.service.notification.nano.DNDPolicyProto;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -32,9 +33,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ZenPolicyTest extends UiServiceTestCase {
+    private static final String CLASS = "android.service.notification.ZenPolicy";
 
     @Test
     public void testZenPolicyApplyAllowedToDisallowed() {
@@ -524,6 +529,66 @@
         assertProtoMatches(policy, policy.toProto());
     }
 
+    @Test
+    public void testTooLongLists_fromParcel() {
+        ArrayList<Integer> longList = new ArrayList<Integer>(50);
+        for (int i = 0; i < 50; i++) {
+            longList.add(ZenPolicy.STATE_UNSET);
+        }
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.build();
+
+        try {
+            Field priorityCategories = Class.forName(CLASS).getDeclaredField(
+                    "mPriorityCategories");
+            priorityCategories.setAccessible(true);
+            priorityCategories.set(policy, longList);
+
+            Field visualEffects = Class.forName(CLASS).getDeclaredField("mVisualEffects");
+            visualEffects.setAccessible(true);
+            visualEffects.set(policy, longList);
+        } catch (NoSuchFieldException e) {
+            fail(e.toString());
+        } catch (ClassNotFoundException e) {
+            fail(e.toString());
+        } catch (IllegalAccessException e) {
+            fail(e.toString());
+        }
+
+        Parcel parcel = Parcel.obtain();
+        policy.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
+
+        // Confirm that all the fields are accessible and UNSET
+        assertAllPriorityCategoriesUnsetExcept(fromParcel, -1);
+        assertAllVisualEffectsUnsetExcept(fromParcel, -1);
+
+        // Because we don't access the lists directly, we also need to use reflection to make sure
+        // the lists are the right length.
+        try {
+            Field priorityCategories = Class.forName(CLASS).getDeclaredField(
+                    "mPriorityCategories");
+            priorityCategories.setAccessible(true);
+            ArrayList<Integer> pcList = (ArrayList<Integer>) priorityCategories.get(fromParcel);
+            assertEquals(ZenPolicy.NUM_PRIORITY_CATEGORIES, pcList.size());
+
+
+            Field visualEffects = Class.forName(CLASS).getDeclaredField("mVisualEffects");
+            visualEffects.setAccessible(true);
+            ArrayList<Integer> veList = (ArrayList<Integer>) visualEffects.get(fromParcel);
+            assertEquals(ZenPolicy.NUM_VISUAL_EFFECTS, veList.size());
+        } catch (NoSuchFieldException e) {
+            fail(e.toString());
+        } catch (ClassNotFoundException e) {
+            fail(e.toString());
+        } catch (IllegalAccessException e) {
+            fail(e.toString());
+        }
+    }
+
     private void assertAllPriorityCategoriesUnsetExcept(ZenPolicy policy, int except) {
         if (except != ZenPolicy.PRIORITY_CATEGORY_REMINDERS) {
             assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryReminders());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 8bd4148..d6cfd00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
@@ -414,9 +415,10 @@
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.setVisibleRequested(true);
-        doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+        doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any(), anyInt());
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
-                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
+                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
+                GRAMMATICAL_GENDER_NOT_SPECIFIED);
         verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
     }
 
@@ -425,8 +427,9 @@
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.setVisibleRequested(true);
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
-                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
-        verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
+                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
+                GRAMMATICAL_GENDER_NOT_SPECIFIED);
+        verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
         verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
     }
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
index efe242c..d3a5885 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
@@ -32,6 +32,7 @@
 
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
+import java.util.ArrayList;
 
 public class MeshActivity extends Activity {
     @Override
@@ -64,7 +65,9 @@
             vertexBuffer.put(5, 400.0f);
             vertexBuffer.rewind();
             Mesh mesh = Mesh.make(
-                    meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+                    meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+
+            canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
 
             int numTriangles = 100;
             // number of triangles plus first 2 vertices
@@ -95,12 +98,10 @@
             }
             iVertexBuffer.rewind();
             indexBuffer.rewind();
-            Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102,
-                    indexBuffer, new Rect(0, 0, 1000, 1000));
-
+            Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer,
+                    new Rect(0, 0, 1000, 1000));
             Paint paint = new Paint();
             paint.setColor(Color.RED);
-            canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
             canvas.drawMesh(mesh2, BlendMode.COLOR, paint);
         }
 
@@ -114,10 +115,9 @@
                     + "      color = vec4(1.0, 0.0, 0.0, 1.0);"
                     + "      return varyings.position;\n"
                     + "}";
-            Attribute[] attList =
-                    new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")};
-            Varying[] varyList =
-                    new MeshSpecification.Varying[] {};
+            ArrayList<Attribute> attList = new ArrayList<>();
+            attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
+            ArrayList<Varying> varyList = new ArrayList<>();
             return MeshSpecification.make(attList, 8, varyList, vs, fs);
         }
     }
diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
new file mode 100644
index 0000000..b24ac3c
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2022 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.app;
+
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.LocaleStore.LocaleInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Unit tests for the {@link AppLocaleCollector}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppLocaleCollectorTest {
+    private static final String TAG = "AppLocaleCollectorTest";
+    private AppLocaleCollector mAppLocaleCollector;
+    private LocaleStore.LocaleInfo mAppCurrentLocale;
+    private Set<LocaleInfo> mAllAppActiveLocales;
+    private Set<LocaleInfo> mImeLocales;
+    private List<LocaleInfo> mSystemCurrentLocales;
+    private Set<LocaleInfo> mSystemSupportedLocales;
+    private AppLocaleStore.AppLocaleResult mResult;
+    private static final String PKG1 = "pkg1";
+    private static final int NONE = LocaleInfo.SUGGESTION_TYPE_NONE;
+    private static final int SIM = LocaleInfo.SUGGESTION_TYPE_SIM;
+    private static final int CFG = LocaleInfo.SUGGESTION_TYPE_CFG;
+    private static final int SIM_CFG = SIM | CFG;
+    private static final int CURRENT = LocaleInfo.SUGGESTION_TYPE_CURRENT;
+    private static final int SYSTEM = LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE;
+    private static final int OTHERAPP = LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE;
+
+    @Before
+    public void setUp() throws Exception {
+        mAppLocaleCollector = spy(
+                new AppLocaleCollector(InstrumentationRegistry.getContext(), PKG1));
+
+        mAppCurrentLocale = createLocaleInfo("en-US", CURRENT);
+        mAllAppActiveLocales = initAllAppActivatedLocales();
+        mImeLocales = initImeLocales();
+        mSystemSupportedLocales = initSystemSupportedLocales();
+        mSystemCurrentLocales = initSystemCurrentLocales();
+        mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG,
+                initAppSupportedLocale());
+    }
+
+    @Test
+    public void testGetSupportedLocaleList() {
+        doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale();
+        doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales();
+        doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales();
+        doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales();
+        doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale(
+                anyObject(), eq(null), eq(true));
+        doReturn(mSystemCurrentLocales).when(mAppLocaleCollector).getSystemCurrentLocale();
+
+        Set<LocaleInfo> result = mAppLocaleCollector.getSupportedLocaleList(null, true, false);
+
+        HashMap<String, Integer> expectedResult = getExpectedResult();
+        assertEquals(result.size(), expectedResult.size());
+        for (LocaleInfo source : result) {
+            int suggestionFlags = expectedResult.getOrDefault(source.getId(), -1);
+            assertEquals(source.mSuggestionFlags, suggestionFlags);
+        }
+    }
+
+    private HashMap<String, Integer> getExpectedResult() {
+        HashMap<String, Integer> map = new HashMap<>();
+        map.put("en-US", CURRENT); // The locale current App activates.
+        map.put("fr", NONE); // The locale App and system support.
+        map.put("zu", NONE); // The locale App and system support.
+        map.put("en", NONE); // Use en because System supports en while APP supports en-CA, en-GB.
+        map.put("ko", NONE); // The locale App and system support.
+        map.put("en-AU", OTHERAPP); // The locale other App activates and current App supports.
+        map.put("en-CA", OTHERAPP); // The locale other App activates and current App supports.
+        map.put("ja-JP", OTHERAPP); // The locale other App activates and current App supports.
+        map.put("zh-Hant-TW", SIM);  // The locale system activates.
+        map.put(createLocaleInfo("", SYSTEM).getId(), SYSTEM); // System language title
+        return map;
+    }
+
+    private Set<LocaleInfo> initSystemSupportedLocales() {
+        return Set.of(
+                createLocaleInfo("en", NONE),
+                createLocaleInfo("fr", NONE),
+                createLocaleInfo("zu", NONE),
+                createLocaleInfo("ko", NONE),
+                // will be filtered because current App doesn't support.
+                createLocaleInfo("es-US", SIM_CFG)
+        );
+    }
+
+    private List<LocaleInfo> initSystemCurrentLocales() {
+        return List.of(createLocaleInfo("zh-Hant-TW", SIM),
+                // will be filtered because current App activates this locale.
+                createLocaleInfo("en-US", SIM));
+    }
+
+    private Set<LocaleInfo> initAllAppActivatedLocales() {
+        return Set.of(
+                createLocaleInfo("en-CA", OTHERAPP),
+                createLocaleInfo("en-AU", OTHERAPP),
+                createLocaleInfo("ja-JP", OTHERAPP),
+                // will be filtered because current App activates this locale.
+                createLocaleInfo("en-US", OTHERAPP));
+    }
+
+    private Set<LocaleInfo> initImeLocales() {
+        return Set.of(
+                // will be filtered because system activates zh-Hant-TW.
+                createLocaleInfo("zh-TW", OTHERAPP),
+                // will be filtered because current App's activats this locale.
+                createLocaleInfo("en-US", OTHERAPP));
+    }
+
+    private HashSet<Locale> initAppSupportedLocale() {
+        HashSet<Locale> hs = new HashSet();
+        hs.add(Locale.forLanguageTag("en-US"));
+        hs.add(Locale.forLanguageTag("en-CA"));
+        hs.add(Locale.forLanguageTag("en-GB"));
+        hs.add(Locale.forLanguageTag("zh-TW"));
+        hs.add(Locale.forLanguageTag("ja"));
+        hs.add(Locale.forLanguageTag("fr"));
+        hs.add(Locale.forLanguageTag("zu"));
+        hs.add(Locale.forLanguageTag("ko"));
+        // will be filtered because it's not in the system language.
+        hs.add(Locale.forLanguageTag("mn"));
+        return hs;
+    }
+
+    private LocaleInfo createLocaleInfo(String languageTag, int suggestionFlag) {
+        LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag(languageTag));
+        localeInfo.mSuggestionFlags = suggestionFlag;
+        localeInfo.setTranslated(true);
+        return localeInfo;
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
new file mode 100644
index 0000000..bf6ece1
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.LocaleStore.LocaleInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for the {@link LocaleStore}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocaleStoreTest {
+    @Before
+    public void setUp() {
+    }
+
+    @Test
+    public void testTransformImeLanguageTagToLocaleInfo() {
+        List<InputMethodSubtype> list = List.of(
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+        Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+        Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+        assertEquals(localeSet.size(), expectedLanguageTag.size());
+        for (LocaleInfo info : localeSet) {
+            assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE);
+            assertTrue(expectedLanguageTag.contains(info.getId()));
+        }
+    }
+}