Merge "Trigger transition in ATMS#resizeTask"
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 a77040c..945531f 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";
@@ -10033,6 +10051,7 @@
     field public static final String NFC_SERVICE = "nfc";
     field public static final String NOTIFICATION_SERVICE = "notification";
     field public static final String NSD_SERVICE = "servicediscovery";
+    field public static final String OVERLAY_SERVICE = "overlay";
     field public static final String PEOPLE_SERVICE = "people";
     field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
     field public static final String POWER_SERVICE = "power";
@@ -11201,6 +11220,49 @@
 
 }
 
+package android.content.om {
+
+  public class FabricatedOverlay {
+    ctor public FabricatedOverlay(@NonNull String, @NonNull String);
+    method @NonNull public android.content.om.OverlayIdentifier getIdentifier();
+    method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
+    method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
+    method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
+    method public void setTargetOverlayable(@Nullable String);
+  }
+
+  public final class OverlayIdentifier implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayIdentifier> CREATOR;
+  }
+
+  public final class OverlayInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.content.om.OverlayIdentifier getOverlayIdentifier();
+    method @Nullable public String getOverlayName();
+    method @Nullable public String getTargetOverlayableName();
+    method @NonNull public String getTargetPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
+  }
+
+  public class OverlayManager {
+    method @NonNull @NonUiContext public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@NonNull String);
+  }
+
+  public final class OverlayManagerTransaction implements android.os.Parcelable {
+    ctor public OverlayManagerTransaction(@NonNull android.content.om.OverlayManager);
+    method @NonUiContext public void commit() throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+    method public int describeContents();
+    method @NonNull public void registerFabricatedOverlay(@NonNull android.content.om.FabricatedOverlay);
+    method @NonNull public void unregisterFabricatedOverlay(@NonNull android.content.om.OverlayIdentifier);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayManagerTransaction> CREATOR;
+  }
+
+}
+
 package android.content.pm {
 
   public class ActivityInfo extends android.content.pm.ComponentInfo implements android.os.Parcelable {
@@ -11217,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
@@ -11765,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);
@@ -11893,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();
@@ -11943,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);
@@ -12203,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";
@@ -12831,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);
@@ -12860,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
@@ -13107,6 +13180,7 @@
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadOverlay(@NonNull android.content.om.OverlayInfo) throws java.io.IOException;
   }
 
 }
@@ -14881,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;
@@ -19917,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;
   }
@@ -19927,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>);
   }
 
@@ -25552,6 +25630,7 @@
   public abstract static class MediaProjection.Callback {
     ctor public MediaProjection.Callback();
     method public void onCapturedContentResize(int, int);
+    method public void onCapturedContentVisibilityChanged(boolean);
     method public void onStop();
   }
 
@@ -41088,6 +41167,7 @@
     field public static final int ROUTE_BLUETOOTH = 2; // 0x2
     field public static final int ROUTE_EARPIECE = 1; // 0x1
     field public static final int ROUTE_SPEAKER = 8; // 0x8
+    field public static final int ROUTE_STREAMING = 16; // 0x10
     field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
@@ -41099,6 +41179,7 @@
     method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
   }
 
   public final class CallEndpoint implements android.os.Parcelable {
@@ -41134,6 +41215,8 @@
   public interface CallEventCallback {
     method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
+    method public void onCallStreamingFailed(int);
+    method public void onCallStreamingStarted(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -41199,6 +41282,18 @@
     method public android.telecom.CallScreeningService.CallResponse.Builder setSkipNotification(boolean);
   }
 
+  public abstract class CallStreamingService extends android.app.Service {
+    ctor public CallStreamingService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public void onCallStreamingStarted(@NonNull android.telecom.StreamingCall);
+    method public void onCallStreamingStateChanged(int);
+    method public void onCallStreamingStopped();
+    field public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+    field public static final int STREAMING_FAILED_ALREADY_STREAMING = 1; // 0x1
+    field public static final int STREAMING_FAILED_NO_SENDER = 2; // 0x2
+    field public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3; // 0x3
+  }
+
   public abstract class Conference extends android.telecom.Conferenceable {
     ctor public Conference(android.telecom.PhoneAccountHandle);
     method public final boolean addConnection(android.telecom.Connection);
@@ -41664,6 +41759,7 @@
     field public static final int CAPABILITY_RTT = 4096; // 0x1000
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+    field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 524288; // 0x80000
     field public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 262144; // 0x40000
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
     field public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 65536; // 0x10000
@@ -41853,6 +41949,22 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
   }
 
+  public final class StreamingCall implements android.os.Parcelable {
+    ctor public StreamingCall(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.net.Uri, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.net.Uri getAddress();
+    method @NonNull public android.content.ComponentName getComponentName();
+    method @NonNull public String getDisplayName();
+    method @NonNull public android.os.Bundle getExtras();
+    method public int getState();
+    method public void setStreamingState(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
+    field public static final int STATE_DISCONNECTED = 3; // 0x3
+    field public static final int STATE_HOLDING = 2; // 0x2
+    field public static final int STATE_STREAMING = 1; // 0x1
+  }
+
   public class TelecomManager {
     method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 97b9a51..c09c6c5 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";
@@ -3429,15 +3433,10 @@
 package android.content.om {
 
   public final class OverlayInfo implements android.os.Parcelable {
-    method public int describeContents();
     method @Nullable public String getCategory();
     method @NonNull public String getPackageName();
-    method @Nullable public String getTargetOverlayableName();
-    method @NonNull public String getTargetPackageName();
     method public int getUserId();
     method public boolean isEnabled();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
   }
 
   public class OverlayManager {
@@ -3452,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;
@@ -3572,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);
@@ -3628,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;
@@ -3646,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);
@@ -3673,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";
@@ -3785,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;
@@ -10928,6 +10960,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 42acc22..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);
@@ -3256,6 +3256,7 @@
     method public int getDisplayId();
     method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
+    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index ce99119..558dae5 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -59,6 +59,15 @@
         }
     }
 
+    /** Reports {@link android.app.servertransaction.RefreshCallbackItem} is executed. */
+    public void activityRefreshed(IBinder token) {
+        try {
+            getActivityClientController().activityRefreshed(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Reports after {@link Activity#onTopResumedActivityChanged(boolean)} is called for losing the
      * top most position.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 37749e6..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);
@@ -5342,6 +5353,11 @@
         }
     }
 
+    @Override
+    public void reportRefresh(ActivityClientRecord r) {
+        ActivityClient.getInstance().activityRefreshed(r.token);
+    }
+
     private void handleSetCoreSettings(Bundle coreSettings) {
         synchronized (mCoreSettingsLock) {
             mCoreSettings = coreSettings;
@@ -6061,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 f322ca9..3ba5783 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -139,6 +139,9 @@
     /** Restart the activity after it was stopped. */
     public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
 
+     /** Report that activity was refreshed to server. */
+    public abstract void reportRefresh(@NonNull ActivityClientRecord r);
+
     /** Set pending activity configuration in case it will be updated by other transaction item. */
     public abstract void updatePendingActivityConfiguration(@NonNull IBinder token,
             Configuration overrideConfig);
@@ -181,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/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 286b84c..ecea46a 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -39,6 +39,7 @@
 interface IActivityClientController {
     oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
     oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
+    oneway void activityRefreshed(in IBinder token);
     /**
      * This call is not one-way because {@link #activityPaused()) is not one-way, or
      * the top-resumed-lost could be reported after activity paused.
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/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index d94f08b..b159f33 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -38,6 +38,9 @@
         return UNDEFINED;
     }
 
+    boolean shouldHaveDefinedPreExecutionState() {
+        return true;
+    }
 
     // Parcelable
 
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/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
new file mode 100644
index 0000000..74abab2
--- /dev/null
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -0,0 +1,145 @@
+/*
+ * 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.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Callback that allows to {@link TransactionExecutor#cycleToPath} to {@link ON_PAUSE} or
+ * {@link ON_STOP} in {@link TransactionExecutor#executeCallbacks} for activity "refresh" flow
+ * that goes through "paused -> resumed" or "stopped -> resumed" cycle.
+ *
+ * <p>This is used in combination with {@link com.android.server.wm.DisplayRotationCompatPolicy}
+ * for camera compatibility treatment that handles orientation mismatch between camera buffers and
+ * an app window. This allows to clear cached values in apps (e.g. display or camera rotation) that
+ * influence camera preview and can lead to sideways or stretching issues.
+ *
+ * @hide
+ */
+public class RefreshCallbackItem extends ActivityTransactionItem {
+
+    // Whether refresh should happen using the "stopped -> resumed" cycle or
+    // "paused -> resumed" cycle.
+    @LifecycleState
+    private int mPostExecutionState;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client,
+            @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions) {}
+
+    @Override
+    public void postExecute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = getActivityClientRecord(client, token);
+        client.reportRefresh(r);
+    }
+
+    @Override
+    public int getPostExecutionState() {
+        return mPostExecutionState;
+    }
+
+    @Override
+    boolean shouldHaveDefinedPreExecutionState() {
+        return false;
+    }
+
+    // ObjectPoolItem implementation
+
+    @Override
+    public void recycle() {
+        ObjectPool.recycle(this);
+    }
+
+    /**
+    * Obtain an instance initialized with provided params.
+    * @param postExecutionState indicating whether refresh should happen using the
+    *        "stopped -> resumed" cycle or "paused -> resumed" cycle.
+    */
+    public static RefreshCallbackItem obtain(@LifecycleState int postExecutionState) {
+        if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
+            throw new IllegalArgumentException(
+                    "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
+                            + "RefreshCallbackItem but got " + postExecutionState);
+        }
+        RefreshCallbackItem instance =
+                ObjectPool.obtain(RefreshCallbackItem.class);
+        if (instance == null) {
+            instance = new RefreshCallbackItem();
+        }
+        instance.mPostExecutionState = postExecutionState;
+        return instance;
+    }
+
+    private RefreshCallbackItem() {}
+
+    // Parcelable implementation
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mPostExecutionState);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final RefreshCallbackItem other = (RefreshCallbackItem) o;
+        return mPostExecutionState == other.mPostExecutionState;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mPostExecutionState;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "RefreshCallbackItem{mPostExecutionState=" + mPostExecutionState + "}";
+    }
+
+    private RefreshCallbackItem(Parcel in) {
+        mPostExecutionState = in.readInt();
+    }
+
+    public static final @NonNull Creator<RefreshCallbackItem> CREATOR =
+            new Creator<RefreshCallbackItem>() {
+
+        public RefreshCallbackItem createFromParcel(Parcel in) {
+            return new RefreshCallbackItem(in);
+        }
+
+        public RefreshCallbackItem[] newArray(int size) {
+            return new RefreshCallbackItem[size];
+        }
+    };
+}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index de1d38a..1ff0b79 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -126,10 +126,13 @@
             final ClientTransactionItem item = callbacks.get(i);
             if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
             final int postExecutionState = item.getPostExecutionState();
-            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
-                    item.getPostExecutionState());
-            if (closestPreExecutionState != UNDEFINED) {
-                cycleToPath(r, closestPreExecutionState, transaction);
+
+            if (item.shouldHaveDefinedPreExecutionState()) {
+                final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+                        item.getPostExecutionState());
+                if (closestPreExecutionState != UNDEFINED) {
+                    cycleToPath(r, closestPreExecutionState, transaction);
+                }
             }
 
             item.execute(mTransactionHandler, token, mPendingActions);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 708a02d..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 {}
@@ -5784,7 +5787,6 @@
      *
      * @see #getSystemService(String)
      * @see android.content.om.OverlayManager
-     * @hide
      */
     public static final String OVERLAY_SERVICE = "overlay";
 
@@ -6170,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.
      *
@@ -7281,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/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index e4936bc..7e787c9 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -78,16 +78,14 @@
  *
  * @see OverlayManager
  * @see OverlayManagerTransaction
- * @hide
  */
 public class FabricatedOverlay {
 
     /**
      * Retrieves the identifier for this fabricated overlay.
      * @return the overlay identifier
-     *
-     * @hide
      */
+    @NonNull
     public OverlayIdentifier getIdentifier() {
         return new OverlayIdentifier(
                 mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
@@ -325,7 +323,6 @@
      * @param overlayName a name used to uniquely identify the fabricated overlay owned by the
      *                   caller itself.
      * @param targetPackage the name of the package to be overlaid
-     * @hide
      */
     public FabricatedOverlay(@NonNull String overlayName, @NonNull String targetPackage) {
         this(generateFabricatedOverlayInternal(
@@ -344,7 +341,6 @@
      * should specify which overlayable to be overlaid.
      *
      * @param targetOverlayable the overlayable name defined in target package.
-     * @hide
      */
     public void setTargetOverlayable(@Nullable String targetOverlayable) {
         mOverlay.targetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
@@ -438,7 +434,6 @@
      * @param value the integer representing the new value
      * @param configuration The string representation of the config this overlay is enabled for
      * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type
-     * @hide
      */
     @NonNull
     public void setResourceValue(
@@ -470,7 +465,6 @@
      * @param value the string representing the new value
      * @param configuration The string representation of the config this overlay is enabled for
      * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type
-     * @hide
      */
     @NonNull
     public void setResourceValue(
@@ -491,7 +485,6 @@
      *     [package]:type/entry)
      * @param value the file descriptor whose contents are the value of the frro
      * @param configuration The string representation of the config this overlay is enabled for
-     * @hide
      */
     @NonNull
     public void setResourceValue(
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
index a43091e..f256372 100644
--- a/core/java/android/content/om/OverlayIdentifier.java
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -41,7 +41,6 @@
  * @see OverlayInfo#getOverlayIdentifier()
  * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
  */
-/** @hide */
 @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
         genEqualsHashCode = true, genToString = false)
 public final class OverlayIdentifier implements Parcelable {
@@ -176,7 +175,6 @@
 
     /**
      * {@inheritDoc}
-     * @hide
      */
     @Override
     @DataClass.Generated.Member
@@ -194,7 +192,6 @@
 
     /**
      * {@inheritDoc}
-     * @hide
      */
     @Override
     @DataClass.Generated.Member
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index a81d16ab..ff1c088 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -46,9 +46,7 @@
  * -->
  *
  * @see OverlayManager#getOverlayInfosForTarget(String)
- * @hide
  */
-@SystemApi
 public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
 
     /** @hide */
@@ -59,7 +57,6 @@
             STATE_DISABLED,
             STATE_ENABLED,
             STATE_ENABLED_IMMUTABLE,
-            // @Deprecated STATE_TARGET_IS_BEING_REPLACED,
             STATE_OVERLAY_IS_BEING_REPLACED,
             STATE_SYSTEM_UPDATE_UNINSTALL,
     })
@@ -312,7 +309,6 @@
      * Get the overlay name from the registered fabricated overlay.
      *
      * @return the overlay name
-     * @hide
      */
     @Override
     @Nullable
@@ -324,10 +320,8 @@
      * Returns the name of the target overlaid package.
      *
      * @return the target package name
-     * @hide
      */
     @Override
-    @SystemApi
     @NonNull
     public String getTargetPackageName() {
         return targetPackageName;
@@ -359,9 +353,7 @@
      * Return the target overlayable name.
      *
      * @return the name of the target overlayable resources set
-     * @hide
      */
-    @SystemApi
     @Override
     @Nullable
     public String getTargetOverlayableName() {
@@ -394,7 +386,6 @@
      *
      * @return an identifier representing the current overlay.
      * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
-     * @hide
      */
     @Override
     @NonNull
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7803cb8..96b7603 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,9 +54,7 @@
  * </ul>
  *
  * @see OverlayManagerTransaction
- * @hide
  */
-@SystemApi
 @SystemService(Context.OVERLAY_SERVICE)
 public class OverlayManager {
 
@@ -392,7 +390,6 @@
      *
      * @param targetPackageName the target package name
      * @return a list of overlay information
-     * @hide
      */
     @NonNull
     @NonUiContext
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index c7c605d..5fd695b 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -58,7 +58,6 @@
  *
  * @see OverlayManager
  * @see FabricatedOverlay
- * @hide
  */
 public final class OverlayManagerTransaction implements Parcelable {
     // TODO: remove @hide from this class when OverlayManager is added to the
@@ -92,8 +91,6 @@
     /**
      * Get an overlay manager transaction with the specified handler.
      * @param overlayManager handles this transaction.
-     *
-     * @hide
      */
     public OverlayManagerTransaction(@NonNull OverlayManager overlayManager) {
         this(new ArrayList<>(), Objects.requireNonNull(overlayManager));
@@ -291,8 +288,6 @@
 
     /**
      * {@inheritDoc}
-     *
-     * @hide
      */
     @Override
     public int describeContents() {
@@ -301,8 +296,6 @@
 
     /**
      * {@inheritDoc}
-     *
-     * @hide
      */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -340,7 +333,6 @@
      *
      * @throws IOException if there is a file operation error.
      * @throws PackageManager.NameNotFoundException if the package name is not found.
-     * @hide
      */
     @NonUiContext
     public void commit() throws PackageManager.NameNotFoundException, IOException {
@@ -374,8 +366,6 @@
      * package or target overlayable is changed.
      *
      * @param overlay the overlay to register with the overlay manager
-     *
-     * @hide
      */
     @NonNull
     public void registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
@@ -389,7 +379,6 @@
      *
      * @see OverlayManager#getOverlayInfosForTarget(String)
      * @see OverlayInfo#getOverlayIdentifier()
-     * @hide
      */
     @NonNull
     public void unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
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/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index a5a1fa689..b097bc0 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -83,7 +83,6 @@
      * @return the resources provider instance for the {@code overlayInfo}
      * @throws IOException when the files can't be loaded.
      * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
-     * @hide
      */
     @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
     @NonNull
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/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/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/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index a50e6db..f8df668 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -106,6 +106,10 @@
     /**
      * Event for changes to the network service state (cellular).
      *
+     * <p>Requires {@link Manifest.permission#ACCESS_FINE_LOCATION} or {@link
+     * Manifest.permission#ACCESS_COARSE_LOCATION} depending on the accuracy of the location info
+     * listeners want to get.
+     *
      * @hide
      * @see ServiceStateListener#onServiceStateChanged
      * @see ServiceState
@@ -485,8 +489,9 @@
      * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
      * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
-     * of whether the calling app has carrier privileges.
+     * <p>Requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission in case that
+     * listener want to get location info in {@link CellIdentity} regardless of whether the calling
+     * app has carrier privileges.
      *
      * @hide
      * @see RegistrationFailedListener#onRegistrationFailed
@@ -504,8 +509,9 @@
      * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
      * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
-     * of whether the calling app has carrier privileges.
+     * <p>Requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission in case that
+     * listener want to get {@link BarringInfo} which includes location info in {@link CellIdentity}
+     * regardless of whether the calling app has carrier privileges.
      *
      * @hide
      * @see BarringInfoListener#onBarringInfoChanged
@@ -691,10 +697,8 @@
          * Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will
          * receive all the information in {@link ServiceState}, otherwise the cellIdentity
          * will be null if apps only holding the {@link Manifest.permission#ACCESS_COARSE_LOCATION}
-         * permission.
-         * Network operator name in long/short alphanumeric format and numeric id will be null if
-         * apps holding neither {@link android.Manifest.permission#ACCESS_FINE_LOCATION} nor
-         * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+         * permission. Network operator name in long/short alphanumeric format and numeric id will
+         * be null if apps holding neither {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
          *
          * @see ServiceState#STATE_EMERGENCY_ONLY
          * @see ServiceState#STATE_IN_SERVICE
@@ -1284,6 +1288,9 @@
          * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
          * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
          *
+         * If the calling app doesn't have {@link android.Manifest.permission#ACCESS_FINE_LOCATION},
+         * it will receive {@link CellIdentity} without location-sensitive information included.
+         *
          * @param cellIdentity        the CellIdentity, which must include the globally unique
          *                            identifier
          *                            for the cell (for example, all components of the CGI or ECGI).
@@ -1462,6 +1469,10 @@
          * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
          * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
          *
+         * If the calling app doesn't have {@link android.Manifest.permission#ACCESS_FINE_LOCATION},
+         * it will receive {@link BarringInfo} including {@link CellIdentity} without
+         * location-sensitive information included.
+         *
          * @param barringInfo for all services on the current cell.
          * @see android.telephony.BarringInfo
          */
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/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b8cd7b9..f87b746 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
 import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
@@ -23,11 +24,15 @@
 
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.os.Process;
+import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 
 import java.util.function.Supplier;
 
@@ -48,8 +53,8 @@
     /**
      * Tracks whether {@link WindowInsetsController#show(int)} or
      * {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
-     * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
-     * because the IME is being shown.
+     * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder,
+     * ImeTracker.Token)}, because the IME is being shown.
      */
     private boolean mIsShowRequestedDuringHideAnimation;
 
@@ -76,7 +81,7 @@
                 // Remove IME surface as IME has finished hide animation, if there is no pending
                 // show request.
                 if (!mIsShowRequestedDuringHideAnimation) {
-                    notifyHidden();
+                    notifyHidden(null /* statsToken */);
                     removeSurface();
                 }
             }
@@ -120,7 +125,8 @@
      * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
      */
     @Override
-    public @ShowResult int requestShow(boolean fromIme) {
+    @ShowResult
+    public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump(
                     "ImeInsetsSourceConsumer#requestShow",
@@ -129,6 +135,9 @@
 
         // TODO: ResultReceiver for IME.
         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
+        ImeTracker.get().onProgress(statsToken,
+                ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
+
         if (getControl() == null) {
             // If control is null, schedule to show IME when control is available.
             mIsRequestedVisibleAwaitingControl = true;
@@ -140,16 +149,32 @@
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
-        return getImm().requestImeShow(mController.getHost().getWindowToken())
+        return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken)
                 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
     }
 
     /**
      * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
      * IME insets are hidden.
+     *
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    private void notifyHidden() {
-        getImm().notifyImeHidden(mController.getHost().getWindowToken());
+    private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
+        // Create a new stats token to track the hide request when:
+        //  - we do not already have one, or
+        //  - we do already have one, but we have control and use the passed in token
+        //      for the insets animation already.
+        if (statsToken == null || getControl() != null) {
+            statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
+                    ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+        }
+
+        ImeTracker.get().onProgress(statsToken,
+                ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
+
+        getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
+        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
     }
 
     @Override
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 709bc2b..8abe66a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -41,6 +41,7 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -64,6 +65,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -977,7 +979,14 @@
 
     @Override
     public void show(@InsetsType int types) {
-        show(types, false /* fromIme */, null /* statsToken */);
+        ImeTracker.Token statsToken = null;
+        if ((types & ime()) != 0) {
+            statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+                    SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
+        }
+
+        show(types, false /* fromIme */, statsToken);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -1054,7 +1063,14 @@
 
     @Override
     public void hide(@InsetsType int types) {
-        hide(types, false /* fromIme */, null /* statsToken */);
+        ImeTracker.Token statsToken = null;
+        if ((types & ime()) != 0) {
+            statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+        }
+
+        hide(types, false /* fromIme */, statsToken);
     }
 
     @VisibleForTesting
@@ -1164,13 +1180,17 @@
             if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
             types &= ~mDisabledUserAnimationInsetsTypes;
 
-            if (fromIme && (disabledTypes & ime()) != 0
-                    && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
-                // We've requested IMM to show IME, but the IME is not controllable. We need to
-                // cancel the request.
-                setRequestedVisibleTypes(0 /* visibleTypes */, ime());
-                if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
-                    notifyVisibilityChanged();
+            if ((disabledTypes & ime()) != 0) {
+                ImeTracker.get().onFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+
+                if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
+                    // We've requested IMM to show IME, but the IME is not controllable. We need to
+                    // cancel the request.
+                    setRequestedVisibleTypes(0 /* visibleTypes */, ime());
+                    if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
+                        notifyVisibilityChanged();
+                    }
                 }
             }
         }
@@ -1182,6 +1202,8 @@
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
             return;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+
         cancelExistingControllers(types);
         if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
         mLastStartedAnimTypes |= types;
@@ -1189,7 +1211,7 @@
         final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
 
         Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
-                fromIme, types, controls, animationType);
+                fromIme, types, controls, animationType, statsToken);
         int typesReady = typesReadyPair.first;
         boolean imeReady = typesReadyPair.second;
         if (DEBUG) Log.d(TAG, String.format(
@@ -1288,7 +1310,10 @@
      * @return Pair of (types ready to animate, IME ready to animate).
      */
     private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
-            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) {
+            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
+            @Nullable ImeTracker.Token statsToken) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+
         int typesReady = 0;
         boolean imeReady = true;
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1301,7 +1326,7 @@
             boolean canRun = true;
             if (show) {
                 // Show request
-                switch(consumer.requestShow(fromIme)) {
+                switch(consumer.requestShow(fromIme, statsToken)) {
                     case ShowResult.SHOW_IMMEDIATELY:
                         break;
                     case ShowResult.IME_SHOW_DELAYED:
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index f46eb34..47672a3 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -35,6 +35,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -50,10 +51,14 @@
 public class InsetsSourceConsumer {
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
+    @IntDef(value = {
+            ShowResult.SHOW_IMMEDIATELY,
+            ShowResult.IME_SHOW_DELAYED,
+            ShowResult.IME_SHOW_FAILED
+    })
     @interface ShowResult {
         /**
-         * Window type is ready to be shown, will be shown immidiately.
+         * Window type is ready to be shown, will be shown immediately.
          */
         int SHOW_IMMEDIATELY = 0;
         /**
@@ -71,11 +76,13 @@
     protected final InsetsController mController;
     protected final InsetsState mState;
     private int mId;
-    private final @InsetsType int mType;
+    @InsetsType
+    private final int mType;
 
     private static final String TAG = "InsetsSourceConsumer";
     private final Supplier<Transaction> mTransactionSupplier;
-    private @Nullable InsetsSourceControl mSourceControl;
+    @Nullable
+    private InsetsSourceControl mSourceControl;
     private boolean mHasWindowFocus;
 
     /**
@@ -180,7 +187,7 @@
         return true;
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public InsetsSourceControl getControl() {
         return mSourceControl;
     }
@@ -280,10 +287,16 @@
      * @param fromController {@code true} if request is coming from controller.
      *                       (e.g. in IME case, controller is
      *                       {@link android.inputmethodservice.InputMethodService}).
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     *
+     * @implNote The {@code statsToken} is ignored here, and only handled in
+     * {@link ImeInsetsSourceConsumer} for IME animations only.
+     *
      * @return @see {@link ShowResult}.
      */
-    @VisibleForTesting
-    public @ShowResult int requestShow(boolean fromController) {
+    @VisibleForTesting(visibility = PACKAGE)
+    @ShowResult
+    public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) {
         return ShowResult.SHOW_IMMEDIATELY;
     }
 
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/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 6eae63a..f08f61f 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -40,6 +40,7 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
@@ -61,6 +62,9 @@
     @Nullable
     private static volatile IInputMethodManager sServiceCache = null;
 
+    @Nullable
+    private static volatile IImeTracker sTrackerServiceCache = null;
+
     /**
      * @return {@code true} if {@link IInputMethodManager} is available.
      */
@@ -527,4 +531,137 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @AnyThread
+    @Nullable
+    static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.onRequestShow(uid, origin, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.onRequestHide(uid, origin, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onProgress(statsToken, phase);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onFailed(statsToken, phase);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onCancelled(statsToken, phase);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onShown(@NonNull IBinder statsToken) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onShown(statsToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onHidden(@NonNull IBinder statsToken) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onHidden(statsToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static boolean hasPendingImeVisibilityRequests() {
+        final var service = getImeTrackerService();
+        if (service == null) {
+            return true;
+        }
+        try {
+            return service.hasPendingImeVisibilityRequests();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    private static IImeTracker getImeTrackerService() {
+        var trackerService = sTrackerServiceCache;
+        if (trackerService == null) {
+            final var service = getService();
+            if (service == null) {
+                return null;
+            }
+
+            try {
+                trackerService = service.getImeTrackerService();
+                if (trackerService == null) {
+                    return null;
+                }
+
+                sTrackerServiceCache = trackerService;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return trackerService;
+    }
 }
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 927d769..1bcb040 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,9 +16,6 @@
 
 package android.view.inputmethod;
 
-import static android.view.inputmethod.ImeTracker.Debug.originToString;
-import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +43,46 @@
 
     String TAG = "ImeTracker";
 
+    /** The type of the IME request. */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_SHOW,
+            TYPE_HIDE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Type {}
+
+    /** IME show request type. */
+    int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
+
+    /** IME hide request type. */
+    int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
+
+    /** The status of the IME request. */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_RUN,
+            STATUS_CANCEL,
+            STATUS_FAIL,
+            STATUS_SUCCESS,
+            STATUS_TIMEOUT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Status {}
+
+    /** IME request running. */
+    int STATUS_RUN = ImeProtoEnums.STATUS_RUN;
+
+    /** IME request cancelled. */
+    int STATUS_CANCEL = ImeProtoEnums.STATUS_CANCEL;
+
+    /** IME request failed. */
+    int STATUS_FAIL = ImeProtoEnums.STATUS_FAIL;
+
+    /** IME request succeeded. */
+    int STATUS_SUCCESS = ImeProtoEnums.STATUS_SUCCESS;
+
+    /** IME request timed out. */
+    int STATUS_TIMEOUT = ImeProtoEnums.STATUS_TIMEOUT;
+
     /**
      * The origin of the IME request
      *
@@ -61,25 +98,17 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface Origin {}
 
-    /**
-     * The IME show request originated in the client.
-     */
-    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+    /** The IME show request originated in the client. */
+    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT;
 
-    /**
-     * The IME hide request originated in the client.
-     */
-    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+    /** The IME hide request originated in the client. */
+    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT;
 
-    /**
-     * The IME show request originated in the server.
-     */
-    int ORIGIN_SERVER_START_INPUT = 2;
+    /** The IME show request originated in the server. */
+    int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT;
 
-    /**
-     * The IME hide request originated in the server.
-     */
-    int ORIGIN_SERVER_HIDE_INPUT = 3;
+    /** The IME hide request originated in the server. */
+    int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT;
 
     /**
      * The current phase of the IME request.
@@ -88,6 +117,7 @@
      * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
      */
     @IntDef(prefix = { "PHASE_" }, value = {
+            PHASE_NOT_SET,
             PHASE_CLIENT_VIEW_SERVED,
             PHASE_SERVER_CLIENT_KNOWN,
             PHASE_SERVER_CLIENT_FOCUSED,
@@ -121,6 +151,11 @@
             PHASE_CLIENT_HANDLE_HIDE_INSETS,
             PHASE_CLIENT_APPLY_ANIMATION,
             PHASE_CLIENT_CONTROL_ANIMATION,
+            PHASE_CLIENT_DISABLED_USER_ANIMATION,
+            PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
+            PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
+            PHASE_CLIENT_REQUEST_IME_SHOW,
+            PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN,
             PHASE_CLIENT_ANIMATION_RUNNING,
             PHASE_CLIENT_ANIMATION_CANCEL,
             PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
@@ -129,135 +164,172 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
 
+    int PHASE_NOT_SET = ImeProtoEnums.PHASE_NOT_SET;
+
     /** The view that requested the IME has been served by the IMM. */
-    int PHASE_CLIENT_VIEW_SERVED = 0;
+    int PHASE_CLIENT_VIEW_SERVED = ImeProtoEnums.PHASE_CLIENT_VIEW_SERVED;
 
     /** The IME client that requested the IME has window manager focus. */
-    int PHASE_SERVER_CLIENT_KNOWN = 1;
+    int PHASE_SERVER_CLIENT_KNOWN = ImeProtoEnums.PHASE_SERVER_CLIENT_KNOWN;
 
     /** The IME client that requested the IME has IME focus. */
-    int PHASE_SERVER_CLIENT_FOCUSED = 2;
+    int PHASE_SERVER_CLIENT_FOCUSED = ImeProtoEnums.PHASE_SERVER_CLIENT_FOCUSED;
 
     /** The IME request complies with the current accessibility settings. */
-    int PHASE_SERVER_ACCESSIBILITY = 3;
+    int PHASE_SERVER_ACCESSIBILITY = ImeProtoEnums.PHASE_SERVER_ACCESSIBILITY;
 
     /** The server is ready to run third party code. */
-    int PHASE_SERVER_SYSTEM_READY = 4;
+    int PHASE_SERVER_SYSTEM_READY = ImeProtoEnums.PHASE_SERVER_SYSTEM_READY;
 
     /** Checked the implicit hide request against any explicit show requests. */
-    int PHASE_SERVER_HIDE_IMPLICIT = 5;
+    int PHASE_SERVER_HIDE_IMPLICIT = ImeProtoEnums.PHASE_SERVER_HIDE_IMPLICIT;
 
     /** Checked the not-always hide request against any forced show requests. */
-    int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+    int PHASE_SERVER_HIDE_NOT_ALWAYS = ImeProtoEnums.PHASE_SERVER_HIDE_NOT_ALWAYS;
 
     /** The server is waiting for a connection to the IME. */
-    int PHASE_SERVER_WAIT_IME = 7;
+    int PHASE_SERVER_WAIT_IME = ImeProtoEnums.PHASE_SERVER_WAIT_IME;
 
     /** The server has a connection to the IME. */
-    int PHASE_SERVER_HAS_IME = 8;
+    int PHASE_SERVER_HAS_IME = ImeProtoEnums.PHASE_SERVER_HAS_IME;
 
     /** The server decided the IME should be hidden. */
-    int PHASE_SERVER_SHOULD_HIDE = 9;
+    int PHASE_SERVER_SHOULD_HIDE = ImeProtoEnums.PHASE_SERVER_SHOULD_HIDE;
 
     /** Reached the IME wrapper. */
-    int PHASE_IME_WRAPPER = 10;
+    int PHASE_IME_WRAPPER = ImeProtoEnums.PHASE_IME_WRAPPER;
 
     /** Dispatched from the IME wrapper to the IME. */
-    int PHASE_IME_WRAPPER_DISPATCH = 11;
+    int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH;
 
     /** Reached the IME' showSoftInput method. */
-    int PHASE_IME_SHOW_SOFT_INPUT = 12;
+    int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT;
 
     /** Reached the IME' hideSoftInput method. */
-    int PHASE_IME_HIDE_SOFT_INPUT = 13;
+    int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT;
 
     /** The server decided the IME should be shown. */
-    int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+    int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE;
 
     /** Requested applying the IME visibility in the insets source consumer. */
-    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER =
+            ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER;
 
     /** Applied the IME visibility. */
-    int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+    int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY;
 
     /** Created the show IME runner. */
-    int PHASE_WM_SHOW_IME_RUNNER = 17;
+    int PHASE_WM_SHOW_IME_RUNNER = ImeProtoEnums.PHASE_WM_SHOW_IME_RUNNER;
 
     /** Ready to show IME. */
-    int PHASE_WM_SHOW_IME_READY = 18;
+    int PHASE_WM_SHOW_IME_READY = ImeProtoEnums.PHASE_WM_SHOW_IME_READY;
 
     /** The Window Manager has a connection to the IME insets control target. */
-    int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+    int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET =
+            ImeProtoEnums.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET;
 
     /** Reached the window insets control target's show insets method. */
-    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS =
+            ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS;
 
     /** Reached the window insets control target's hide insets method. */
-    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS =
+            ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS;
 
     /** Reached the remote insets control target's show insets method. */
-    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS =
+            ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS;
 
     /** Reached the remote insets control target's hide insets method. */
-    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS =
+            ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS;
 
     /** Reached the remote insets controller. */
-    int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+    int PHASE_WM_REMOTE_INSETS_CONTROLLER = ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROLLER;
 
     /** Created the IME window insets show animation. */
-    int PHASE_WM_ANIMATION_CREATE = 25;
+    int PHASE_WM_ANIMATION_CREATE = ImeProtoEnums.PHASE_WM_ANIMATION_CREATE;
 
     /** Started the IME window insets show animation. */
-    int PHASE_WM_ANIMATION_RUNNING = 26;
+    int PHASE_WM_ANIMATION_RUNNING = ImeProtoEnums.PHASE_WM_ANIMATION_RUNNING;
 
     /** Reached the client's show insets method. */
-    int PHASE_CLIENT_SHOW_INSETS = 27;
+    int PHASE_CLIENT_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_SHOW_INSETS;
 
     /** Reached the client's hide insets method. */
-    int PHASE_CLIENT_HIDE_INSETS = 28;
+    int PHASE_CLIENT_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HIDE_INSETS;
 
     /** Handling the IME window insets show request. */
-    int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+    int PHASE_CLIENT_HANDLE_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_SHOW_INSETS;
 
     /** Handling the IME window insets hide request. */
-    int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+    int PHASE_CLIENT_HANDLE_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_HIDE_INSETS;
 
     /** Applied the IME window insets show animation. */
-    int PHASE_CLIENT_APPLY_ANIMATION = 31;
+    int PHASE_CLIENT_APPLY_ANIMATION = ImeProtoEnums.PHASE_CLIENT_APPLY_ANIMATION;
 
     /** Started the IME window insets show animation. */
-    int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+    int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
+
+    /** Checked that the IME is controllable. */
+    int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
+
+    /** Collecting insets source controls. */
+    int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
+
+    /** Reached the insets source consumer's show request method. */
+    int PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW =
+            ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW;
+
+    /** Reached input method manager's request IME show method. */
+    int PHASE_CLIENT_REQUEST_IME_SHOW = ImeProtoEnums.PHASE_CLIENT_REQUEST_IME_SHOW;
+
+    /** Reached the insets source consumer's notify hidden method. */
+    int PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN =
+            ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN;
 
     /** Queued the IME window insets show animation. */
-    int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+    int PHASE_CLIENT_ANIMATION_RUNNING = ImeProtoEnums.PHASE_CLIENT_ANIMATION_RUNNING;
 
     /** Cancelled the IME window insets show animation. */
-    int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+    int PHASE_CLIENT_ANIMATION_CANCEL = ImeProtoEnums.PHASE_CLIENT_ANIMATION_CANCEL;
 
     /** Finished the IME window insets show animation. */
-    int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+    int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_SHOW;
 
     /** Finished the IME window insets hide animation. */
-    int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+    int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_HIDE;
 
     /**
-     * Called when an IME show request is created.
+     * Creates an IME show request tracking token.
      *
-     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     * @param component the component name where the IME show request was created,
+     *                  or {@code null} otherwise
+     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
+     *
+     * @return An IME tracking token.
      */
-    void onRequestShow(@Nullable Token token, @Origin int origin,
+    @NonNull
+    Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
             @SoftInputShowHideReason int reason);
 
     /**
-     * Called when an IME hide request is created.
+     * Creates an IME hide request tracking token.
      *
-     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     * @param component the component name where the IME hide request was created,
+     *                  or {@code null} otherwise
+     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
+     *
+     * @return An IME tracking token.
      */
-    void onRequestHide(@Nullable Token token, @Origin int origin,
+    @NonNull
+    Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
             @SoftInputShowHideReason int reason);
 
     /**
@@ -313,112 +385,122 @@
      */
     @NonNull
     static ImeTracker get() {
-        return SystemProperties.getBoolean("persist.debug.imetracker", false)
-                ? LOGGER
-                : NOOP_LOGGER;
+        return LOGGER;
     }
 
     /** The singleton IME tracker instance. */
+    @NonNull
     ImeTracker LOGGER = new ImeTracker() {
 
+        {
+            // Set logging flag initial value.
+            mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
+            // Update logging flag dynamically.
+            SystemProperties.addChangeCallback(() ->
+                    mLogProgress =
+                            SystemProperties.getBoolean("persist.debug.imetracker", false));
+        }
+
+        /** Whether progress should be logged. */
+        private boolean mLogProgress;
+
+        @NonNull
         @Override
-        public void onRequestShow(@Nullable Token token, int origin,
+        public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            if (token == null) return;
-            Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
+            if (binder == null) binder = new Binder();
+
+            final Token token = Token.build(binder, component);
+
+            Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+
+            return token;
         }
 
+        @NonNull
         @Override
-        public void onRequestHide(@Nullable Token token, int origin,
+        public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            if (token == null) return;
-            Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
+            if (binder == null) binder = new Binder();
+
+            final Token token = Token.build(binder, component);
+
+            Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+
+            return token;
         }
 
         @Override
-        public void onProgress(@Nullable Token token, int phase) {
+        public void onProgress(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+            IInputMethodManagerGlobalInvoker.onProgress(token.mBinder, phase);
+
+            if (mLogProgress) {
+                Log.i(TAG, token.mTag + ": onProgress at " + Debug.phaseToString(phase));
+            }
         }
 
         @Override
-        public void onFailed(@Nullable Token token, int phase) {
+        public void onFailed(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+            IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+
+            Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
         }
 
         @Override
-        public void onTodo(@Nullable Token token, int phase) {
+        public void onTodo(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+            Log.i(TAG, token.mTag + ": onTodo at " + Debug.phaseToString(phase));
         }
 
         @Override
-        public void onCancelled(@Nullable Token token, int phase) {
+        public void onCancelled(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+            IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+
+            Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
         }
 
         @Override
         public void onShown(@Nullable Token token) {
             if (token == null) return;
+            IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+
             Log.i(TAG, token.mTag + ": onShown");
         }
 
         @Override
         public void onHidden(@Nullable Token token) {
             if (token == null) return;
+            IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+
             Log.i(TAG, token.mTag + ": onHidden");
         }
     };
 
-    /** The singleton no-op IME tracker instance. */
-    ImeTracker NOOP_LOGGER = new ImeTracker() {
-
-        @Override
-        public void onRequestShow(@Nullable Token token, int origin,
-                @SoftInputShowHideReason int reason) {}
-
-        @Override
-        public void onRequestHide(@Nullable Token token, int origin,
-                @SoftInputShowHideReason int reason) {}
-
-        @Override
-        public void onProgress(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onFailed(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onTodo(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onCancelled(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onShown(@Nullable Token token) {}
-
-        @Override
-        public void onHidden(@Nullable Token token) {}
-    };
-
     /** A token that tracks the progress of an IME request. */
     class Token implements Parcelable {
 
-        private final IBinder mBinder;
+        @NonNull
+        public final IBinder mBinder;
+
+        @NonNull
         private final String mTag;
 
-        public Token() {
-            this(ActivityThread.currentProcessName());
+        @NonNull
+        private static Token build(@NonNull IBinder binder, @Nullable String component) {
+            if (component == null) component = ActivityThread.currentProcessName();
+            final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
+
+            return new Token(binder, tag);
         }
 
-        public Token(String component) {
-            this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
-        }
-
-        private Token(IBinder binder, String tag) {
+        private Token(@NonNull IBinder binder, @NonNull String tag) {
             mBinder = binder;
             mTag = tag;
         }
@@ -443,10 +525,11 @@
 
         @NonNull
         public static final Creator<Token> CREATOR = new Creator<>() {
+            @NonNull
             @Override
             public Token createFromParcel(Parcel source) {
-                IBinder binder = source.readStrongBinder();
-                String tag = source.readString8();
+                final IBinder binder = source.readStrongBinder();
+                final String tag = source.readString8();
                 return new Token(binder, tag);
             }
 
@@ -458,22 +541,34 @@
     }
 
     /**
-     * Utilities for mapping phases and origins IntDef values to their names.
+     * Utilities for mapping IntDef values to their names.
      *
      * Note: This is held in a separate class so that it only gets initialized when actually needed.
      */
     class Debug {
 
+        private static final Map<Integer, String> sTypes =
+                getFieldMapping(ImeTracker.class, "TYPE_");
+        private static final Map<Integer, String> sStatus =
+                getFieldMapping(ImeTracker.class, "STATUS_");
         private static final Map<Integer, String> sOrigins =
                 getFieldMapping(ImeTracker.class, "ORIGIN_");
         private static final Map<Integer, String> sPhases =
                 getFieldMapping(ImeTracker.class, "PHASE_");
 
-        public static String originToString(int origin) {
+        public static String typeToString(@Type int type) {
+            return sTypes.getOrDefault(type, "TYPE_" + type);
+        }
+
+        public static String statusToString(@Status int status) {
+            return sStatus.getOrDefault(status, "STATUS_" + status);
+        }
+
+        public static String originToString(@Origin int origin) {
             return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
         }
 
-        public static String phaseToString(int phase) {
+        public static String phaseToString(@Phase int phase) {
             return sPhases.getOrDefault(phase, "PHASE_" + phase);
         }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8331155..e9f0d29 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2002,14 +2002,16 @@
      * {@link #RESULT_HIDDEN}.
      */
     public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
-        return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
+        return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
+                SoftInputShowHideReason.SHOW_SOFT_INPUT);
     }
 
-    private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-                reason);
+    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        if (statsToken == null) {
+            statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
+        }
 
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
@@ -2057,8 +2059,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
-            final ImeTracker.Token statsToken = new ImeTracker.Token();
-            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+            final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT);
 
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
@@ -2148,9 +2150,8 @@
 
     private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason);
+        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
 
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                 this, null /* icProto */);
@@ -2283,7 +2284,7 @@
                     hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
                             SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
                 } else {
-                    showSoftInput(view, showFlags, null,
+                    showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */,
                             SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
                 }
             }
@@ -2796,8 +2797,8 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
 
         synchronized (mH) {
@@ -2856,18 +2857,23 @@
      *
      * @param windowToken the window from which this request originates. If this doesn't match the
      *                    currently served view, the request is ignored and returns {@code false}.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      *
      * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
      * @hide
      */
-    public boolean requestImeShow(IBinder windowToken) {
+    public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
         checkFocus();
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
                 return false;
             }
-            showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */,
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+
+            showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
             return true;
         }
@@ -2878,12 +2884,15 @@
      *
      * @param windowToken the window from which this request originates. If this doesn't match the
      *                    currently served view, the request is ignored.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      * @hide
      */
-    public void notifyImeHidden(IBinder windowToken) {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+    public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
+        if (statsToken == null) {
+            statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+        }
 
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
@@ -3549,6 +3558,18 @@
     }
 
     /**
+     * A test API for CTS to check whether there are any pending IME visibility requests.
+     *
+     * @return {@code true} iff there are pending IME visibility requests.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    public boolean hasPendingImeVisibilityRequests() {
+        return IInputMethodManagerGlobalInvoker.hasPendingImeVisibilityRequests();
+    }
+
+    /**
      * Show the settings for enabling subtypes of the specified input method.
      *
      * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 500c41c..05717dd 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -31,7 +31,10 @@
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.Spanned;
+import android.text.TextPaint;
 import android.text.method.TransformationMethod;
+import android.text.style.CharacterStyle;
 import android.widget.TextView;
 
 import java.util.Objects;
@@ -182,6 +185,70 @@
         mLinkTextColor = builder.mLinkTextColor;
     }
 
+    /**
+     * Creates a new instance of {@link TextAppearanceInfo} by extracting text appearance from the
+     * character before cursor in the target {@link TextView}.
+     * @param textView the target {@link TextView}.
+     * @return the new instance of {@link TextAppearanceInfo}.
+     * @hide
+     */
+    @NonNull
+    public static TextAppearanceInfo createFromTextView(@NonNull TextView textView) {
+        final int selectionStart = textView.getSelectionStart();
+        final CharSequence text = textView.getText();
+        TextPaint textPaint = new TextPaint();
+        textPaint.set(textView.getPaint());    // Copy from textView
+        if (text instanceof Spanned && text.length() > 0 && selectionStart > 0) {
+            // Extract the CharacterStyle spans that changes text appearance in the character before
+            // cursor.
+            Spanned spannedText = (Spanned) text;
+            int lastCh = selectionStart - 1;
+            CharacterStyle[] spans = spannedText.getSpans(lastCh, lastCh, CharacterStyle.class);
+            if (spans != null) {
+                for (CharacterStyle span: spans) {
+                    // Exclude spans that end at lastCh
+                    if (spannedText.getSpanStart(span) <= lastCh
+                            && lastCh < spannedText.getSpanEnd(span)) {
+                        span.updateDrawState(textPaint); // Override the TextPaint
+                    }
+                }
+            }
+        }
+        Typeface typeface = textPaint.getTypeface();
+        String systemFontFamilyName = null;
+        int textWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+        int textStyle = Typeface.NORMAL;
+        if (typeface != null) {
+            systemFontFamilyName = typeface.getSystemFontFamilyName();
+            textWeight = typeface.getWeight();
+            textStyle = typeface.getStyle();
+        }
+        TextAppearanceInfo.Builder builder = new TextAppearanceInfo.Builder();
+        builder.setTextSize(textPaint.getTextSize())
+                .setTextLocales(textPaint.getTextLocales())
+                .setSystemFontFamilyName(systemFontFamilyName)
+                .setTextFontWeight(textWeight)
+                .setTextStyle(textStyle)
+                .setShadowDx(textPaint.getShadowLayerDx())
+                .setShadowDy(textPaint.getShadowLayerDy())
+                .setShadowRadius(textPaint.getShadowLayerRadius())
+                .setShadowColor(textPaint.getShadowLayerColor())
+                .setElegantTextHeight(textPaint.isElegantTextHeight())
+                .setLetterSpacing(textPaint.getLetterSpacing())
+                .setFontFeatureSettings(textPaint.getFontFeatureSettings())
+                .setFontVariationSettings(textPaint.getFontVariationSettings())
+                .setTextScaleX(textPaint.getTextScaleX())
+                .setTextColor(textPaint.getColor())
+                .setLinkTextColor(textPaint.linkColor)
+                .setAllCaps(textView.isAllCaps())
+                .setFallbackLineSpacing(textView.isFallbackLineSpacing())
+                .setLineBreakStyle(textView.getLineBreakStyle())
+                .setLineBreakWordStyle(textView.getLineBreakWordStyle())
+                .setHighlightTextColor(textView.getHighlightColor())
+                .setHintTextColor(textView.getCurrentHintTextColor());
+        return builder.build();
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0a3ea8a..e643080 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,7 +21,6 @@
 
 import android.R;
 import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,7 +37,6 @@
 import android.content.UndoOwner;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -51,10 +49,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.RenderNode;
-import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.fonts.FontStyle;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -4774,41 +4770,7 @@
             }
 
             if (includeTextAppearance) {
-                Typeface typeface = mTextView.getPaint().getTypeface();
-                String systemFontFamilyName = null;
-                int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
-                if (typeface != null) {
-                    systemFontFamilyName = typeface.getSystemFontFamilyName();
-                    textFontWeight = typeface.getWeight();
-                }
-                ColorStateList linkTextColors = mTextView.getLinkTextColors();
-                @ColorInt int linkTextColor = linkTextColors != null
-                        ? linkTextColors.getDefaultColor() : 0;
-
-                TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
-                appearanceBuilder.setTextSize(mTextView.getTextSize())
-                        .setTextLocales(mTextView.getTextLocales())
-                        .setSystemFontFamilyName(systemFontFamilyName)
-                        .setTextFontWeight(textFontWeight)
-                        .setTextStyle(mTextView.getTypefaceStyle())
-                        .setAllCaps(mTextView.isAllCaps())
-                        .setShadowDx(mTextView.getShadowDx())
-                        .setShadowDy(mTextView.getShadowDy())
-                        .setShadowRadius(mTextView.getShadowRadius())
-                        .setShadowColor(mTextView.getShadowColor())
-                        .setElegantTextHeight(mTextView.isElegantTextHeight())
-                        .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
-                        .setLetterSpacing(mTextView.getLetterSpacing())
-                        .setFontFeatureSettings(mTextView.getFontFeatureSettings())
-                        .setFontVariationSettings(mTextView.getFontVariationSettings())
-                        .setLineBreakStyle(mTextView.getLineBreakStyle())
-                        .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
-                        .setTextScaleX(mTextView.getTextScaleX())
-                        .setHighlightTextColor(mTextView.getHighlightColor())
-                        .setTextColor(mTextView.getCurrentTextColor())
-                        .setHintTextColor(mTextView.getCurrentHintTextColor())
-                        .setLinkTextColor(linkTextColor);
-                builder.setTextAppearanceInfo(appearanceBuilder.build());
+                builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView));
             }
             imm.updateCursorAnchorInfo(mTextView, builder.build());
 
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/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index f1635eb..ec9184b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -23,6 +23,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeProtoEnums;
 import android.view.inputmethod.InputMethodManager;
 
 import java.lang.annotation.Retention;
@@ -64,113 +65,114 @@
         SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED,
         SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION,
-        SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR})
+        SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR
+})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
-    int SHOW_SOFT_INPUT = 0;
+    int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT;
 
     /** Show soft input when {@code InputMethodManagerService#attachNewInputLocked} called. */
-    int ATTACH_NEW_INPUT = 1;
+    int ATTACH_NEW_INPUT = ImeProtoEnums.REASON_ATTACH_NEW_INPUT;
 
     /** Show soft input by {@code InputMethodManagerService#showMySoftInput}. This is triggered when
      *  the IME process try to show the keyboard.
      *
      * @see android.inputmethodservice.InputMethodService#requestShowSelf(int)
      */
-    int SHOW_SOFT_INPUT_FROM_IME = 2;
+    int SHOW_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_FROM_IME;
 
     /**
      * Hide soft input by
      * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow}.
      */
-    int HIDE_SOFT_INPUT = 3;
+    int HIDE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT;
 
     /**
      * Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)}.
      */
-    int HIDE_SOFT_INPUT_FROM_IME = 4;
+    int HIDE_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_IME;
 
     /**
      * Show soft input when navigated forward to the window (with
-     * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}} which the focused view is text
+     * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}) which the focused view is text
      * editor and system will auto-show the IME when the window can resize or running on a large
      * screen.
      */
-    int SHOW_AUTO_EDITOR_FORWARD_NAV = 5;
+    int SHOW_AUTO_EDITOR_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_AUTO_EDITOR_FORWARD_NAV;
 
     /**
      * Show soft input when navigated forward to the window with
      * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
      * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE}.
      */
-    int SHOW_STATE_VISIBLE_FORWARD_NAV = 6;
+    int SHOW_STATE_VISIBLE_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_STATE_VISIBLE_FORWARD_NAV;
 
     /**
      * Show soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE}.
      */
-    int SHOW_STATE_ALWAYS_VISIBLE = 7;
+    int SHOW_STATE_ALWAYS_VISIBLE = ImeProtoEnums.REASON_SHOW_STATE_ALWAYS_VISIBLE;
 
     /**
      * Show soft input during {@code InputMethodManagerService} receive changes from
      * {@code SettingsProvider}.
      */
-    int SHOW_SETTINGS_ON_CHANGE = 8;
+    int SHOW_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_SHOW_SETTINGS_ON_CHANGE;
 
     /** Hide soft input during switching user. */
-    int HIDE_SWITCH_USER = 9;
+    int HIDE_SWITCH_USER = ImeProtoEnums.REASON_HIDE_SWITCH_USER;
 
     /** Hide soft input when the user is invalid. */
-    int HIDE_INVALID_USER = 10;
+    int HIDE_INVALID_USER = ImeProtoEnums.REASON_HIDE_INVALID_USER;
 
     /**
      * Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_UNSPECIFIED} which
      * the focused view is not text editor.
      */
-    int HIDE_UNSPECIFIED_WINDOW = 11;
+    int HIDE_UNSPECIFIED_WINDOW = ImeProtoEnums.REASON_HIDE_UNSPECIFIED_WINDOW;
 
     /**
      * Hide soft input when navigated forward to the window with
      * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
      * {@link LayoutParams#SOFT_INPUT_STATE_HIDDEN}.
      */
-    int HIDE_STATE_HIDDEN_FORWARD_NAV = 12;
+    int HIDE_STATE_HIDDEN_FORWARD_NAV = ImeProtoEnums.REASON_HIDE_STATE_HIDDEN_FORWARD_NAV;
 
     /**
      * Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}.
      */
-    int HIDE_ALWAYS_HIDDEN_STATE = 13;
+    int HIDE_ALWAYS_HIDDEN_STATE = ImeProtoEnums.REASON_HIDE_ALWAYS_HIDDEN_STATE;
 
     /** Hide soft input when "adb shell ime <command>" called. */
-    int HIDE_RESET_SHELL_COMMAND = 14;
+    int HIDE_RESET_SHELL_COMMAND = ImeProtoEnums.REASON_HIDE_RESET_SHELL_COMMAND;
 
     /**
      * Hide soft input during {@code InputMethodManagerService} receive changes from
      * {@code SettingsProvider}.
      */
-    int HIDE_SETTINGS_ON_CHANGE = 15;
+    int HIDE_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_HIDE_SETTINGS_ON_CHANGE;
 
     /**
      * Hide soft input from {@link com.android.server.policy.PhoneWindowManager} when setting
      * {@link com.android.internal.R.integer#config_shortPressOnPowerBehavior} in config.xml as
      * dismiss IME.
      */
-    int HIDE_POWER_BUTTON_GO_HOME = 16;
+    int HIDE_POWER_BUTTON_GO_HOME = ImeProtoEnums.REASON_HIDE_POWER_BUTTON_GO_HOME;
 
     /** Hide soft input when attaching docked stack. */
-    int HIDE_DOCKED_STACK_ATTACHED = 17;
+    int HIDE_DOCKED_STACK_ATTACHED = ImeProtoEnums.REASON_HIDE_DOCKED_STACK_ATTACHED;
 
     /**
      * Hide soft input when {@link com.android.server.wm.RecentsAnimationController} starts
      * intercept touch from app window.
      */
-    int HIDE_RECENTS_ANIMATION = 18;
+    int HIDE_RECENTS_ANIMATION = ImeProtoEnums.REASON_HIDE_RECENTS_ANIMATION;
 
     /**
      * Hide soft input when {@link com.android.wm.shell.bubbles.BubbleController} is expanding,
      * switching, or collapsing Bubbles.
      */
-    int HIDE_BUBBLES = 19;
+    int HIDE_BUBBLES = ImeProtoEnums.REASON_HIDE_BUBBLES;
 
     /**
      * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
@@ -183,74 +185,78 @@
      * only the dialog focused as it's the latest window with input focus) makes we need to hide
      * soft-input when the same window focused again to align with the same behavior prior to R.
      */
-    int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
+    int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR =
+            ImeProtoEnums.REASON_HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR;
 
     /**
      * Hide soft input when a {@link com.android.internal.inputmethod.IInputMethodClient} is
      * removed.
      */
-    int HIDE_REMOVE_CLIENT = 21;
+    int HIDE_REMOVE_CLIENT = ImeProtoEnums.REASON_HIDE_REMOVE_CLIENT;
 
     /**
      * Show soft input when the system invoking
      * {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}.
      */
-    int SHOW_RESTORE_IME_VISIBILITY = 22;
+    int SHOW_RESTORE_IME_VISIBILITY = ImeProtoEnums.REASON_SHOW_RESTORE_IME_VISIBILITY;
 
     /**
      * Show soft input by
      * {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
      */
-    int SHOW_TOGGLE_SOFT_INPUT = 23;
+    int SHOW_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_TOGGLE_SOFT_INPUT;
 
     /**
      * Hide soft input by
      * {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
      */
-    int HIDE_TOGGLE_SOFT_INPUT = 24;
+    int HIDE_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_TOGGLE_SOFT_INPUT;
 
     /**
      * Show soft input by
      * {@link android.view.InsetsController#show(int)};
      */
-    int SHOW_SOFT_INPUT_BY_INSETS_API = 25;
+    int SHOW_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_BY_INSETS_API;
 
     /**
      * Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
      * See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
      */
-    int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
+    int HIDE_DISPLAY_IME_POLICY_HIDE = ImeProtoEnums.REASON_HIDE_DISPLAY_IME_POLICY_HIDE;
 
     /**
      * Hide soft input by {@link android.view.InsetsController#hide(int)}.
      */
-    int HIDE_SOFT_INPUT_BY_INSETS_API = 27;
+    int HIDE_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_INSETS_API;
 
     /**
      * Hide soft input by {@link android.inputmethodservice.InputMethodService#handleBack(boolean)}.
      */
-    int HIDE_SOFT_INPUT_BY_BACK_KEY = 28;
+    int HIDE_SOFT_INPUT_BY_BACK_KEY = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_BACK_KEY;
 
     /**
      * Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}.
      */
-    int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = 29;
+    int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT =
+            ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT;
 
     /**
      * Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#onExtractingInputChanged(EditorInfo)})}.
      */
-    int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED = 30;
+    int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED =
+            ImeProtoEnums.REASON_HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED;
 
     /**
      * Hide soft input by the deprecated
      * {@link InputMethodManager#hideSoftInputFromInputMethod(IBinder, int)}.
      */
-    int HIDE_SOFT_INPUT_IMM_DEPRECATION = 31;
+    int HIDE_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IMM_DEPRECATION;
 
     /**
      * Hide soft input when the window gained focus without an editor from the IME shown window.
      */
-    int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR = 32;
+    int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR =
+            ImeProtoEnums.REASON_HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR;
 }
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/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/view/IImeTracker.aidl
new file mode 100644
index 0000000..b062ca7
--- /dev/null
+++ b/core/java/com/android/internal/view/IImeTracker.aidl
@@ -0,0 +1,94 @@
+/*
+ * 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.view;
+
+import android.view.inputmethod.ImeTracker;
+
+/**
+ * Interface to the global Ime tracker, used by all client applications.
+ * {@hide}
+ */
+interface IImeTracker {
+
+    /**
+     * Called when an IME show request is created,
+     * returns a new Binder to be associated with the IME tracking token.
+     *
+     * @param uid the uid of the client that requested the IME.
+     * @param origin the origin of the IME show request.
+     * @param reason the reason why the IME show request was created.
+     */
+    IBinder onRequestShow(int uid, int origin, int reason);
+
+    /**
+     * Called when an IME hide request is created,
+     * returns a new Binder to be associated with the IME tracking token.
+     *
+     * @param uid the uid of the client that requested the IME.
+     * @param origin the origin of the IME hide request.
+     * @param reason the reason why the IME hide request was created.
+     */
+    IBinder onRequestHide(int uid, int origin, int reason);
+
+    /**
+     * Called when the IME request progresses to a further phase.
+     *
+     * @param statsToken the token tracking the current IME request.
+     * @param phase the new phase the IME request reached.
+     */
+    oneway void onProgress(in IBinder statsToken, int phase);
+
+    /**
+     * Called when the IME request fails.
+     *
+     * @param statsToken the token tracking the current IME request.
+     * @param phase the phase the IME request failed at.
+     */
+    oneway void onFailed(in IBinder statsToken, int phase);
+
+    /**
+     * Called when the IME request is cancelled.
+     *
+     * @param statsToken the token tracking the current IME request.
+     * @param phase the phase the IME request was cancelled at.
+     */
+    oneway void onCancelled(in IBinder statsToken, int phase);
+
+    /**
+     * Called when the IME show request is successful.
+     *
+     * @param statsToken the token tracking the current IME request.
+     */
+    oneway void onShown(in IBinder statsToken);
+
+    /**
+     * Called when the IME hide request is successful.
+     *
+     * @param statsToken the token tracking the current IME request.
+     */
+    oneway void onHidden(in IBinder statsToken);
+
+    /**
+     * Checks whether there are any pending IME visibility requests.
+     *
+     * @return {@code true} iff there are pending IME visibility requests.
+     */
+    @EnforcePermission("TEST_INPUT_METHOD")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.TEST_INPUT_METHOD)")
+    boolean hasPendingImeVisibilityRequests();
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 00bc3f2..2106426 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -27,6 +27,7 @@
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.view.IImeTracker;
 
 /**
  * Public interface to the global input method manager, used by all client
@@ -158,4 +159,10 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.TEST_INPUT_METHOD)")
     void setStylusWindowIdleTimeoutForTest(in IInputMethodClient client, long timeout);
+
+    /**
+     * Returns the singleton instance for the Ime Tracker Service.
+     * {@hide}
+     */
+    IImeTracker getImeTrackerService();
 }
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 307707f..35ff7e8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5366,6 +5366,12 @@
          TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
     <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
 
+    <!-- Whether camera compat treatment is enabled for issues caused by orientation mismatch
+        between camera buffers and an app window. This includes force rotation of fixed
+        orientation activities connected to the camera in fullscreen and showing a tooltip in
+        split screen. -->
+    <bool name="config_isWindowManagerCameraCompatTreatmentEnabled">false</bool>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">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 923ef32..c73f2f4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4432,6 +4432,7 @@
   <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
   <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
+  <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
   <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
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/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index c917302..5ec93e5 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -71,6 +71,7 @@
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -136,7 +137,8 @@
                         private boolean mImeRequestedShow;
 
                         @Override
-                        public int requestShow(boolean fromController) {
+                        public int requestShow(boolean fromController,
+                                ImeTracker.Token statsToken) {
                             if (fromController || mImeRequestedShow) {
                                 mImeRequestedShow = true;
                                 return SHOW_IMMEDIATELY;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 3a3eeee..0486e3c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -43,6 +43,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -221,7 +222,8 @@
                     return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
                             () -> mMockTransaction, controller) {
                         @Override
-                        public int requestShow(boolean fromController) {
+                        public int requestShow(boolean fromController,
+                                ImeTracker.Token statsToken) {
                             return SHOW_IMMEDIATELY;
                         }
                     };
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
new file mode 100644
index 0000000..f93cd18
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StyleSpan;
+import android.text.style.TypefaceSpan;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextAppearanceInfoTest {
+    private static final float EPSILON = 0.0000001f;
+    private static final String TEST_TEXT = "Happy birthday!";
+    private static final float TEXT_SIZE = 16.5f;
+    private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja");
+    private static final String FONT_FAMILY_NAME = "sans-serif";
+    private static final int TEXT_WEIGHT = FontStyle.FONT_WEIGHT_MEDIUM;
+    private static final int TEXT_STYLE = Typeface.ITALIC;
+    private static final boolean ALL_CAPS = true;
+    private static final float SHADOW_DX = 2.0f;
+    private static final float SHADOW_DY = 2.0f;
+    private static final float SHADOW_RADIUS = 2.0f;
+    private static final int SHADOW_COLOR = Color.GRAY;
+    private static final boolean ELEGANT_TEXT_HEIGHT = true;
+    private static final boolean FALLBACK_LINE_SPACING = true;
+    private static final float LETTER_SPACING = 5.0f;
+    private static final String FONT_FEATURE_SETTINGS = "smcp";
+    private static final String FONT_VARIATION_SETTINGS = "'wdth' 1.0";
+    private static final int LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_LOOSE;
+    private static final int LINE_BREAK_WORD_STYLE = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE;
+    private static final float TEXT_SCALEX = 1.5f;
+    private static final int HIGHLIGHT_TEXT_COLOR = Color.YELLOW;
+    private static final int TEXT_COLOR = Color.RED;
+    private static final int HINT_TEXT_COLOR = Color.GREEN;
+    private static final int LINK_TEXT_COLOR = Color.BLUE;
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private final EditText mEditText = new EditText(mContext);
+    private final SpannableStringBuilder mSpannableText = new SpannableStringBuilder(TEST_TEXT);
+    private Canvas mCanvas;
+
+    @Before
+    public void setUp() {
+        mEditText.setText(mSpannableText);
+        mEditText.getPaint().setTextSize(TEXT_SIZE);
+        mEditText.setTextLocales(TEXT_LOCALES);
+        Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
+        mEditText.setTypeface(
+                Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
+        mEditText.setAllCaps(ALL_CAPS);
+        mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
+        mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
+        mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
+        mEditText.setLetterSpacing(LETTER_SPACING);
+        mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
+        mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
+        mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
+        mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
+        mEditText.setTextScaleX(TEXT_SCALEX);
+        mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
+        mEditText.setTextColor(TEXT_COLOR);
+        mEditText.setHintTextColor(HINT_TEXT_COLOR);
+        mEditText.setLinkTextColor(LINK_TEXT_COLOR);
+        ViewGroup.LayoutParams params =
+                new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        mEditText.setLayoutParams(params);
+        mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        Bitmap bitmap =
+                Bitmap.createBitmap(
+                        Math.max(1, mEditText.getMeasuredWidth()),
+                        Math.max(1, mEditText.getMeasuredHeight()),
+                        Bitmap.Config.ARGB_8888);
+        mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
+        mCanvas = new Canvas(bitmap);
+        mEditText.draw(mCanvas);
+    }
+
+    @Test
+    public void testCreateFromTextView_noSpan() {
+        assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText));
+    }
+
+    @Test
+    public void testCreateFromTextView_withSpan1() {
+        AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30);
+        mSpannableText.setSpan(sizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN);
+        mSpannableText.setSpan(colorSpan, 1, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        TypefaceSpan typefaceSpan = new TypefaceSpan("cursive");
+        mSpannableText.setSpan(typefaceSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        mEditText.setText(mSpannableText);
+
+        // |Happy birthday!
+        mEditText.setSelection(0);
+        TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info1.getTextSize(), TEXT_SIZE, EPSILON);
+        assertEquals(info1.getTextColor(), TEXT_COLOR);
+        assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // H|appy birthday!
+        mEditText.setSelection(1);
+        TextAppearanceInfo info2 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info2.getTextSize(), 30f, EPSILON);
+        assertEquals(info2.getTextColor(), TEXT_COLOR);
+        assertEquals(info2.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // Ha|ppy birthday!
+        mEditText.setSelection(2);
+        TextAppearanceInfo info3 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info3.getTextSize(), 30f, EPSILON);
+        assertEquals(info3.getTextColor(), Color.CYAN);
+        assertEquals(info3.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // Ha[ppy birthday!]
+        mEditText.setSelection(2, mSpannableText.length());
+        TextAppearanceInfo info4 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info4.getTextSize(), 30f, EPSILON);
+        assertEquals(info4.getTextColor(), Color.CYAN);
+        assertEquals(info4.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // Happy| birthday!
+        mEditText.setSelection(5);
+        TextAppearanceInfo info5 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info5.getTextSize(), 30f, EPSILON);
+        assertEquals(info5.getTextColor(), Color.CYAN);
+        assertEquals(info5.getSystemFontFamilyName(), "cursive");
+    }
+
+    @Test
+    public void testCreateFromTextView_withSpan2() {
+        // aab|
+        SpannableStringBuilder spannableText = new SpannableStringBuilder("aab");
+
+        AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30);
+        spannableText.setSpan(sizeSpan, 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+        ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN);
+        spannableText.setSpan(colorSpan, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
+        spannableText.setSpan(styleSpan, 1, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+
+        TypefaceSpan typefaceSpan = new TypefaceSpan("cursive");
+        spannableText.setSpan(typefaceSpan, 3, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+        ScaleXSpan scaleXSpan = new ScaleXSpan(2.0f);
+        spannableText.setSpan(scaleXSpan, 3, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        mEditText.setText(spannableText);
+        mEditText.setSelection(3);
+        TextAppearanceInfo info = TextAppearanceInfo.createFromTextView(mEditText);
+
+        // The character before cursor 'b' should only have an AbsoluteSizeSpan.
+        assertEquals(info.getTextSize(), 30f, EPSILON);
+        assertEquals(info.getTextColor(), TEXT_COLOR);
+        assertEquals(info.getTextStyle(), TEXT_STYLE);
+        assertEquals(info.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+        assertEquals(info.getTextScaleX(), TEXT_SCALEX, EPSILON);
+    }
+
+    @Test
+    public void testCreateFromTextView_contradictorySpans() {
+        // Set multiple contradictory spans
+        AbsoluteSizeSpan sizeSpan1 = new AbsoluteSizeSpan(30);
+        CustomForegroundColorSpan colorSpan1 = new CustomForegroundColorSpan(Color.BLUE);
+        AbsoluteSizeSpan sizeSpan2 = new AbsoluteSizeSpan(10);
+        CustomForegroundColorSpan colorSpan2 = new CustomForegroundColorSpan(Color.GREEN);
+
+        mSpannableText.setSpan(sizeSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannableText.setSpan(colorSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannableText.setSpan(sizeSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannableText.setSpan(colorSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        mEditText.setText(mSpannableText);
+        mEditText.draw(mCanvas);
+        mEditText.setSelection(3);
+        // Get a copy of the real TextPaint after setting the last span
+        TextPaint realTextPaint = colorSpan2.lastTextPaint;
+        assertNotNull(realTextPaint);
+        TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText);
+        // Verify the real TextPaint equals the last span of multiple contradictory spans
+        assertEquals(info1.getTextSize(), 10f, EPSILON);
+        assertEquals(info1.getTextSize(), realTextPaint.getTextSize(), EPSILON);
+        assertEquals(info1.getTextColor(), Color.GREEN);
+        assertEquals(info1.getTextColor(), realTextPaint.getColor());
+        assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+    }
+
+    private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) {
+        assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON);
+        assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES);
+        assertEquals(textAppearanceInfo.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+        assertEquals(textAppearanceInfo.getTextFontWeight(), TEXT_WEIGHT);
+        assertEquals(textAppearanceInfo.getTextStyle(), TEXT_STYLE);
+        assertEquals(textAppearanceInfo.isAllCaps(), ALL_CAPS);
+        assertEquals(textAppearanceInfo.getShadowRadius(), SHADOW_RADIUS, EPSILON);
+        assertEquals(textAppearanceInfo.getShadowDx(), SHADOW_DX, EPSILON);
+        assertEquals(textAppearanceInfo.getShadowDy(), SHADOW_DY, EPSILON);
+        assertEquals(textAppearanceInfo.getShadowColor(), SHADOW_COLOR);
+        assertEquals(textAppearanceInfo.isElegantTextHeight(), ELEGANT_TEXT_HEIGHT);
+        assertEquals(textAppearanceInfo.isFallbackLineSpacing(), FALLBACK_LINE_SPACING);
+        assertEquals(textAppearanceInfo.getLetterSpacing(), LETTER_SPACING, EPSILON);
+        assertEquals(textAppearanceInfo.getFontFeatureSettings(), FONT_FEATURE_SETTINGS);
+        assertEquals(textAppearanceInfo.getFontVariationSettings(), FONT_VARIATION_SETTINGS);
+        assertEquals(textAppearanceInfo.getLineBreakStyle(), LINE_BREAK_STYLE);
+        assertEquals(textAppearanceInfo.getLineBreakWordStyle(), LINE_BREAK_WORD_STYLE);
+        assertEquals(textAppearanceInfo.getTextScaleX(), TEXT_SCALEX, EPSILON);
+        assertEquals(textAppearanceInfo.getTextColor(), TEXT_COLOR);
+        assertEquals(textAppearanceInfo.getHighlightTextColor(), HIGHLIGHT_TEXT_COLOR);
+        assertEquals(textAppearanceInfo.getHintTextColor(), HINT_TEXT_COLOR);
+        assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR);
+    }
+
+    static class CustomForegroundColorSpan extends ForegroundColorSpan {
+        @Nullable public TextPaint lastTextPaint = null;
+
+        CustomForegroundColorSpan(int color) {
+            super(color);
+        }
+
+        CustomForegroundColorSpan(@NonNull Parcel src) {
+            super(src);
+        }
+
+        @Override
+        public void updateDrawState(@NonNull TextPaint tp) {
+            super.updateDrawState(tp);
+            // Copy the real TextPaint
+            TextPaint tpCopy = new TextPaint();
+            tpCopy.set(tp);
+            lastTextPaint = tpCopy;
+        }
+    }
+}
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/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bddf452..bc3af1d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -313,6 +313,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-1812743677": {
+      "message": "Display id=%d is ignoring all orientation requests, camera is active and the top activity is eligible for force rotation, return %s,portrait activity: %b, is natural orientation portrait: %b.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-1810446914": {
       "message": "Trying to update display configuration for system\/invalid process.",
       "level": "WARN",
@@ -493,6 +499,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1631991057": {
+      "message": "Display id=%d is notified that Camera %s is closed but activity is still refreshing. Rescheduling an update.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-1630752478": {
       "message": "removeLockedTask: removed %s",
       "level": "DEBUG",
@@ -637,6 +649,12 @@
       "group": "WM_DEBUG_WINDOW_INSETS",
       "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
     },
+    "-1480918485": {
+      "message": "Refreshed activity: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1480772131": {
       "message": "No app or window is requesting an orientation, return %d for display id=%d",
       "level": "VERBOSE",
@@ -1375,6 +1393,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-799396645": {
+      "message": "Display id=%d is notified that Camera %s is closed, updating rotation.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-799003045": {
       "message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
       "level": "VERBOSE",
@@ -1603,6 +1627,12 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-627759820": {
+      "message": "Display id=%d is notified that Camera %s is open for package %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-622997754": {
       "message": "postWindowRemoveCleanupLocked: %s",
       "level": "VERBOSE",
@@ -1633,12 +1663,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-576580969": {
-      "message": "viewServerWindowCommand: bootFinished() failed.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-576070986": {
       "message": "Performing post-rotate rotation after seamless rotation",
       "level": "INFO",
@@ -1981,6 +2005,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-254406860": {
+      "message": "Unable to tell MediaProjectionManagerService about visibility change on the active projection: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-251259736": {
       "message": "No longer freezing: %s",
       "level": "VERBOSE",
@@ -2161,6 +2191,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/Session.java"
     },
+    "-81260230": {
+      "message": "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-81121442": {
       "message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s",
       "level": "ERROR",
@@ -4177,12 +4213,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "1903353011": {
-      "message": "notifyAppStopped: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1912291550": {
       "message": "Sleep still waiting to pause %s",
       "level": "VERBOSE",
@@ -4243,6 +4273,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "1967643923": {
+      "message": "Refershing activity for camera compatibility treatment, activityRecord=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "1967975839": {
       "message": "Changing app %s visible=%b performLayout=%b",
       "level": "VERBOSE",
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/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/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
index 2c8de2e..147d74c 100644
--- a/media/java/android/media/projection/IMediaProjectionCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -20,4 +20,5 @@
 oneway interface IMediaProjectionCallback {
     void onStop();
     void onCapturedContentResize(int width, int height);
+    void onCapturedContentVisibilityChanged(boolean isVisible);
 }
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a63d02b..99d1f8d 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -48,7 +48,11 @@
     void notifyActiveProjectionCapturedContentResized(int width, int height);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
-            + ".permission.MANAGE_MEDIA_PROJECTION)")
+                + ".permission.MANAGE_MEDIA_PROJECTION)")
+    void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+                + ".permission.MANAGE_MEDIA_PROJECTION)")
     void addCallback(IMediaProjectionWatcherCallback callback);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3dfff1f..985ac3c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -66,13 +66,20 @@
         }
     }
 
-    /** Register a listener to receive notifications about when the {@link
-     * MediaProjection} changes state.
+    /**
+     * Register a listener to receive notifications about when the {@link MediaProjection} or
+     * captured content changes state.
+     * <p>
+     * The callback should be registered before invoking
+     * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+     * Handler)}
+     * to ensure that any notifications on the callback are not missed.
+     * </p>
      *
      * @param callback The callback to call.
-     * @param handler The handler on which the callback should be invoked, or
-     * null if the callback should be invoked on the calling thread's looper.
-     *
+     * @param handler  The handler on which the callback should be invoked, or
+     *                 null if the callback should be invoked on the calling thread's looper.
+     * @throws IllegalArgumentException If the given callback is null.
      * @see #unregisterCallback
      */
     public void registerCallback(Callback callback, Handler handler) {
@@ -85,10 +92,11 @@
         mCallbacks.put(callback, new CallbackRecord(callback, handler));
     }
 
-    /** Unregister a MediaProjection listener.
+    /**
+     * Unregister a {@link MediaProjection} listener.
      *
      * @param callback The callback to unregister.
-     *
+     * @throws IllegalArgumentException If the given callback is null.
      * @see #registerCallback
      */
     public void unregisterCallback(Callback callback) {
@@ -283,6 +291,34 @@
          * }</pre>
          */
         public void onCapturedContentResize(int width, int height) { }
+
+        /**
+         * Indicates the visibility of the captured region has changed. Called immediately after
+         * capture begins with the initial visibility state, and when visibility changes. Provides
+         * the app with accurate state for presenting its own UI. The application can take advantage
+         * of this by showing or hiding the captured content, based on if the captured region is
+         * currently visible to the user.
+         * <p>
+         * For example, if the user elected to capture a single app (from the activity shown from
+         * {@link MediaProjectionManager#createScreenCaptureIntent()}), the callback may be
+         * triggered for the following reasons:
+         * <ul>
+         *     <li>
+         *         The captured region may become visible ({@code isVisible} with value
+         *         {@code true}), because the captured app is at least partially visible. This may
+         *         happen if the captured app was previously covered by another app. The other app
+         *         moves to show at least some portion of the captured app.
+         *     </li>
+         *     <li>
+         *         The captured region may become invisible ({@code isVisible} with value
+         *         {@code false}) if it is entirely hidden. This may happen if the captured app is
+         *         entirely covered by another app, or the user navigates away from the captured
+         *         app.
+         *     </li>
+         * </ul>
+         * </p>
+         */
+        public void onCapturedContentVisibilityChanged(boolean isVisible) { }
     }
 
     private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -299,6 +335,13 @@
                 cbr.onCapturedContentResize(width, height);
             }
         }
+
+        @Override
+        public void onCapturedContentVisibilityChanged(boolean isVisible) {
+            for (CallbackRecord cbr : mCallbacks.values()) {
+                cbr.onCapturedContentVisibilityChanged(isVisible);
+            }
+        }
     }
 
     private final static class CallbackRecord {
@@ -322,5 +365,9 @@
         public void onCapturedContentResize(int width, int height) {
             mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
         }
+
+        public void onCapturedContentVisibilityChanged(boolean isVisible) {
+            mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible));
+        }
     }
 }
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/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/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index eeb8368..b7fb294 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -214,17 +214,21 @@
       GetCredentialProviderData.Builder("io.enpass.app")
         .setCredentialEntries(
           listOf<Entry>(
+              newGetEntry(
+                  "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
+                  "elisa.family@outlook.com", null, 3L
+              ),
             newGetEntry(
               "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
-              "elisa.bakery@gmail.com", "Elisa Beckett", 300L
+              "elisa.bakery@gmail.com", "Elisa Beckett", 0L
             ),
             newGetEntry(
               "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
-              "elisa.bakery@gmail.com", null, 300L
+              "elisa.bakery@gmail.com", null, 10L
             ),
             newGetEntry(
-              "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
-              "elisa.family@outlook.com", null, 100L
+              "key1", "subkey-3", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+              "elisa.family@outlook.com", "Elisa Beckett", 1L
             ),
           )
         ).setAuthenticationEntry(
@@ -247,12 +251,12 @@
         .setCredentialEntries(
           listOf<Entry>(
             newGetEntry(
-              "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
-              "elisa.family@outlook.com", null, 600L
+              "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.family@outlook.com", null, 4L
             ),
             newGetEntry(
-              "key1", "subkey-2", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
-              "elisa.family@outlook.com", null, 100L
+                  "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
+                  "elisa.work@outlook.com", null, 11L
             ),
           )
         ).setAuthenticationEntry(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 5e7f1e0..ac0db5a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -146,13 +146,19 @@
                 textAlign = TextAlign.Center,
                 style = MaterialTheme.typography.headlineSmall,
                 text = stringResource(
-                    if (sortedUserNameToCredentialEntryList.size == 1) {
-                        if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
-                                .first().credentialType
+                    if (sortedUserNameToCredentialEntryList
+                            .size == 1 && authenticationEntryList.isEmpty()
+                    ) {
+                        if (sortedUserNameToCredentialEntryList.first()
+                                .sortedCredentialEntryList.first().credentialType
                             == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
-                        )
-                            R.string.get_dialog_title_use_passkey_for
+                        ) R.string.get_dialog_title_use_passkey_for
                         else R.string.get_dialog_title_use_sign_in_for
+                    } else if (
+                        sortedUserNameToCredentialEntryList
+                            .isEmpty() && authenticationEntryList.size == 1
+                    ) {
+                        R.string.get_dialog_title_use_sign_in_for
                     } else R.string.get_dialog_title_choose_sign_in_for,
                     requestDisplayInfo.appDomainName
                 ),
@@ -164,20 +170,46 @@
                     .padding(horizontal = 24.dp)
                     .align(alignment = Alignment.CenterHorizontally)
             ) {
+                val usernameForCredentialSize = sortedUserNameToCredentialEntryList
+                    .size
+                val authenticationEntrySize = authenticationEntryList.size
                 LazyColumn(
                     verticalArrangement = Arrangement.spacedBy(2.dp)
                 ) {
-                    items(sortedUserNameToCredentialEntryList) {
-                        CredentialEntryRow(
-                            credentialEntryInfo = it.sortedCredentialEntryList.first(),
-                            onEntrySelected = onEntrySelected,
-                        )
-                    }
-                    items(authenticationEntryList) {
-                        AuthenticationEntryRow(
-                            authenticationEntryInfo = it,
-                            onEntrySelected = onEntrySelected,
-                        )
+                    // Show max 4 entries in this primary page
+                    if (usernameForCredentialSize + authenticationEntrySize <= 4) {
+                        items(sortedUserNameToCredentialEntryList) {
+                            CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                        items(authenticationEntryList) {
+                            AuthenticationEntryRow(
+                                authenticationEntryInfo = it,
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                    } else if (usernameForCredentialSize < 4) {
+                        items(sortedUserNameToCredentialEntryList) {
+                            CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                        items(authenticationEntryList.take(4 - usernameForCredentialSize)) {
+                            AuthenticationEntryRow(
+                                authenticationEntryInfo = it,
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                    } else {
+                        items(sortedUserNameToCredentialEntryList.take(4)) {
+                            CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
                     }
                 }
             }
@@ -257,7 +289,7 @@
                         )
                     }
                     // Locked password manager
-                    if (!authenticationEntryList.isEmpty()) {
+                    if (authenticationEntryList.isNotEmpty()) {
                         item {
                             LockedCredentials(
                                 authenticationEntryList = authenticationEntryList,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index c182397..294e468 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -182,7 +182,7 @@
   Preconditions.checkState(remoteEntryList.size <= 1)
 
   // Compose sortedUserNameToCredentialEntryList
-  val comparator = CredentialEntryInfoComparator()
+  val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
   // Sort per username
   userNameToCredentialEntryMap.values.forEach {
     it.sortWith(comparator)
@@ -191,7 +191,7 @@
   val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
     PerUserNameCredentialEntryList(it.key, it.value)
   }.sortedWith(
-    compareBy(comparator) { it.sortedCredentialEntryList.first() }
+    compareByDescending{ it.sortedCredentialEntryList.first().lastUsedTimeMillis }
   )
 
   return ProviderDisplayInfo(
@@ -219,7 +219,7 @@
     GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
 }
 
-internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
+internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {
   override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
     // First prefer passkey type for its security benefits
     if (p0.credentialType != p1.credentialType) {
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/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/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/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/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/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/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9922818..7b8ca91 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -493,9 +493,7 @@
 
     private boolean isLocationPermissionRequired(Set<Integer> events) {
         return events.contains(TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_REGISTRATION_FAILURE)
-                || events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED);
+                || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED);
     }
 
     private boolean isPhoneStatePermissionRequired(Set<Integer> events, String callingPackage,
@@ -1002,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="
@@ -1028,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="
@@ -1359,15 +1365,19 @@
                 }
                 if (events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED)) {
                     BarringInfo barringInfo = mBarringInfo.get(r.phoneId);
-                    BarringInfo biNoLocation = barringInfo != null
-                            ? barringInfo.createLocationInfoSanitizedCopy() : null;
-                    if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
-                    try {
-                        r.callback.onBarringInfoChanged(
-                                checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
-                                        ? barringInfo : biNoLocation);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if (VDBG) {
+                        log("listen: call onBarringInfoChanged=" + barringInfo);
+                    }
+                    if (barringInfo != null) {
+                        BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+
+                        try {
+                            r.callback.onBarringInfoChanged(
+                                    checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
+                                            ? barringInfo : biNoLocation);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
                 }
                 if (events.contains(
@@ -3618,29 +3628,21 @@
 
     private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
             @Nullable String callingFeatureId, String message) {
-        LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
-                new LocationAccessPolicy.LocationPermissionQuery.Builder()
-                        .setCallingPackage(callingPackage)
-                        .setCallingFeatureId(callingFeatureId)
-                        .setMethod(message + " events: " + events)
-                        .setCallingPid(Binder.getCallingPid())
-                        .setCallingUid(Binder.getCallingUid());
-
-        boolean shouldCheckLocationPermissions = false;
-
+        boolean isPermissionCheckSuccessful = true;
         if (isLocationPermissionRequired(events)) {
+            LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
+                    new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                            .setCallingPackage(callingPackage)
+                            .setCallingFeatureId(callingFeatureId)
+                            .setMethod(message + " events: " + events)
+                            .setCallingPid(Binder.getCallingPid())
+                            .setCallingUid(Binder.getCallingUid());
             // Everything that requires fine location started in Q. So far...
             locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q);
             // If we're enforcing fine starting in Q, we also want to enforce coarse even for
             // older SDK versions.
             locationQueryBuilder.setMinSdkVersionForCoarse(0);
             locationQueryBuilder.setMinSdkVersionForEnforcement(0);
-            shouldCheckLocationPermissions = true;
-        }
-
-        boolean isPermissionCheckSuccessful = true;
-
-        if (shouldCheckLocationPermissions) {
             LocationAccessPolicy.LocationPermissionResult result =
                     LocationAccessPolicy.checkLocationPermission(
                             mContext, locationQueryBuilder.build());
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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b505396..43c8032d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11510,6 +11510,11 @@
             public void onCapturedContentResize(int width, int height) {
                 // Ignore resize of the captured content.
             }
+
+            @Override
+            public void onCapturedContentVisibilityChanged(boolean isVisible) {
+                // Ignore visibility changes of the captured content.
+            }
         };
         UnregisterOnStopCallback mProjectionCallback;
 
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/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index e16ca0b..0b03005 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -74,6 +74,7 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.framework.protobuf.nano.MessageNano;
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -389,6 +390,16 @@
             return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
         }
 
+        // When config_isWindowManagerCameraCompatTreatmentEnabled is true,
+        // DisplayRotationCompatPolicy in WindowManager force rotates fullscreen activities with
+        // fixed orientation to align them with the natural orientation of the device.
+        if (ctx.getResources().getBoolean(
+                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled)) {
+            Slog.v(TAG, "Disable Rotate and Crop to avoid conflicts with"
+                    + " WM force rotation treatment.");
+            return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+        }
+
         // External cameras do not need crop-rotate-scale.
         if (lensFacing != CameraMetadata.LENS_FACING_FRONT
                 && lensFacing != CameraMetadata.LENS_FACING_BACK) {
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/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 5a5be4e..ddeaa1b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -621,6 +621,13 @@
             // expect), and there will still be letterboxing on the output content since the
             // Surface and VirtualDisplay would then have different aspect ratios.
         }
+
+        @Override
+        public void onCapturedContentVisibilityChanged(boolean isVisible) {
+            // Do nothing when we tell the client that the content has a visibility change - it is
+            // up to them to decide to pause recording, and update their own UI, depending on their
+            // use case.
+        }
     }
 
     @VisibleForTesting
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/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
new file mode 100644
index 0000000..60167b4
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -0,0 +1,338 @@
+/*
+ * 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.inputmethod;
+
+import android.Manifest;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.inputmethod.ImeTracker;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.view.IImeTracker;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayDeque;
+import java.util.Locale;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Service for managing and logging {@link ImeTracker.Token} instances.
+ *
+ * @implNote Suppresses {@link GuardedBy} warnings, as linter reports that {@link #mHistory}
+ * interactions are guarded by {@code this} instead of {@code ImeTrackerService.this}, which should
+ * be identical.
+ *
+ * @hide
+ */
+@SuppressWarnings("GuardedBy")
+public final class ImeTrackerService extends IImeTracker.Stub {
+
+    static final String TAG = "ImeTrackerService";
+
+    /** The threshold in milliseconds after which a history entry is considered timed out. */
+    private static final long TIMEOUT_MS = 10_000;
+
+    /** Handler for registering timeouts for live entries. */
+    private final Handler mHandler =
+            new Handler(Looper.myLooper(), null /* callback */, true /* async */);
+
+    /** Singleton instance of the History. */
+    @GuardedBy("ImeTrackerService.this")
+    private final History mHistory = new History();
+
+    @NonNull
+    @Override
+    public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IBinder binder = new Binder();
+        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
+                ImeTracker.STATUS_RUN, origin, reason);
+        mHistory.addEntry(binder, entry);
+
+        // Register a delayed task to handle the case where the new entry times out.
+        mHandler.postDelayed(() -> {
+            synchronized (ImeTrackerService.this) {
+                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+            }
+        }, TIMEOUT_MS);
+
+        return binder;
+    }
+
+    @NonNull
+    @Override
+    public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IBinder binder = new Binder();
+        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
+                ImeTracker.STATUS_RUN, origin, reason);
+        mHistory.addEntry(binder, entry);
+
+        // Register a delayed task to handle the case where the new entry times out.
+        mHandler.postDelayed(() -> {
+            synchronized (ImeTrackerService.this) {
+                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+            }
+        }, TIMEOUT_MS);
+
+        return binder;
+    }
+
+    @Override
+    public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final History.Entry entry = mHistory.getEntry(statsToken);
+        if (entry == null) return;
+
+        entry.mPhase = phase;
+    }
+
+    @Override
+    public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+    }
+
+    @Override
+    public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+    }
+
+    @Override
+    public synchronized void onShown(@NonNull IBinder statsToken) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    }
+
+    @Override
+    public synchronized void onHidden(@NonNull IBinder statsToken) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    }
+
+    /**
+     * Updates the IME request tracking token with new information available in IMMS.
+     *
+     * @param statsToken the token corresponding to the current IME request.
+     * @param requestWindowName the name of the window that created the IME request.
+     */
+    public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+            @NonNull String requestWindowName) {
+        final History.Entry entry = mHistory.getEntry(statsToken);
+        if (entry == null) return;
+
+        entry.mRequestWindowName = requestWindowName;
+    }
+
+    /** Dumps the contents of the history. */
+    public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        mHistory.dump(pw, prefix);
+    }
+
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public synchronized boolean hasPendingImeVisibilityRequests() {
+        super.hasPendingImeVisibilityRequests_enforcePermission();
+
+        return !mHistory.mLiveEntries.isEmpty();
+    }
+
+    /**
+     * A circular buffer storing the most recent few {@link ImeTracker.Token} entries information.
+     */
+    private static final class History {
+
+        /** The circular buffer's capacity. */
+        private static final int CAPACITY = 100;
+
+        /** Backing store for the circular buffer. */
+        @GuardedBy("ImeTrackerService.this")
+        private final ArrayDeque<Entry> mEntries = new ArrayDeque<>(CAPACITY);
+
+        /** Backing store for the live entries (i.e. entries that are not finished yet). */
+        @GuardedBy("ImeTrackerService.this")
+        private final WeakHashMap<IBinder, Entry> mLiveEntries = new WeakHashMap<>();
+
+        /** Latest entry sequence number. */
+        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+        /** Adds a live entry. */
+        @GuardedBy("ImeTrackerService.this")
+        private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
+            mLiveEntries.put(statsToken, entry);
+        }
+
+        /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+        @Nullable
+        @GuardedBy("ImeTrackerService.this")
+        private Entry getEntry(@NonNull IBinder statsToken) {
+            return mLiveEntries.get(statsToken);
+        }
+
+        /**
+         * Sets the live entry corresponding to the tracking token, if it exists, as finished,
+         * and uploads the data for metrics.
+         *
+         * @param statsToken the token corresponding to the current IME request.
+         * @param status the finish status of the IME request.
+         * @param phase the phase the IME request finished at, if it exists
+         *              (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
+         */
+        @GuardedBy("ImeTrackerService.this")
+        private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
+                @ImeTracker.Phase int phase) {
+            final Entry entry = mLiveEntries.remove(statsToken);
+            if (entry == null) return;
+
+            entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
+            entry.mStatus = status;
+
+            if (phase != ImeTracker.PHASE_NOT_SET) {
+                entry.mPhase = phase;
+            }
+
+            // Remove excess entries overflowing capacity (plus one for the new entry).
+            while (mEntries.size() >= CAPACITY) {
+                mEntries.remove();
+            }
+
+            mEntries.offer(entry);
+
+            // Log newly finished entry.
+            FrameworkStatsLog.write(FrameworkStatsLog.IME_REQUEST_FINISHED, entry.mUid,
+                    entry.mDuration, entry.mType, entry.mStatus, entry.mReason,
+                    entry.mOrigin, entry.mPhase);
+        }
+
+        /** Dumps the contents of the circular buffer. */
+        @GuardedBy("ImeTrackerService.this")
+        private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            final DateTimeFormatter formatter =
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                            .withZone(ZoneId.systemDefault());
+
+            pw.print(prefix);
+            pw.println("ImeTrackerService#History.mLiveEntries:");
+
+            for (final Entry entry: mLiveEntries.values()) {
+                dumpEntry(entry, pw, prefix, formatter);
+            }
+
+            pw.print(prefix);
+            pw.println("ImeTrackerService#History.mEntries:");
+
+            for (final Entry entry: mEntries) {
+                dumpEntry(entry, pw, prefix, formatter);
+            }
+        }
+
+        @GuardedBy("ImeTrackerService.this")
+        private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
+                @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
+            pw.print(prefix);
+            pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+
+            pw.print(prefix);
+            pw.println(" duration=" + entry.mDuration + "ms");
+
+            pw.print(prefix);
+            pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
+
+            pw.print(prefix);
+            pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
+
+            pw.print(prefix);
+            pw.print(" origin="
+                    + ImeTracker.Debug.originToString(entry.mOrigin));
+
+            pw.print(prefix);
+            pw.print(" reason="
+                    + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+
+            pw.print(prefix);
+            pw.print(" phase="
+                    + ImeTracker.Debug.phaseToString(entry.mPhase));
+
+            pw.print(prefix);
+            pw.print(" requestWindowName=" + entry.mRequestWindowName);
+        }
+
+        /** A history entry. */
+        private static final class Entry {
+
+            /** The entry's sequence number in the history. */
+            private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+
+            /** Uid of the client that requested the IME. */
+            private final int mUid;
+
+            /** Clock time in milliseconds when the IME request was created. */
+            private final long mStartTime = System.currentTimeMillis();
+
+            /** Duration in milliseconds of the IME request from start to end. */
+            private long mDuration = 0;
+
+            /** Type of the IME request. */
+            @ImeTracker.Type
+            private final int mType;
+
+            /** Status of the IME request. */
+            @ImeTracker.Status
+            private int mStatus;
+
+            /** Origin of the IME request. */
+            @ImeTracker.Origin
+            private final int mOrigin;
+
+            /** Reason for creating the IME request. */
+            @SoftInputShowHideReason
+            private final int mReason;
+
+            /** Latest phase of the IME request. */
+            @ImeTracker.Phase
+            private int mPhase = ImeTracker.PHASE_NOT_SET;
+
+            /**
+             * Name of the window that created the IME request.
+             *
+             * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+             */
+            @NonNull
+            private String mRequestWindowName = "not set";
+
+            private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
+                    @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+                mUid = uid;
+                mType = type;
+                mStatus = status;
+                mOrigin = origin;
+                mReason = reason;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5840acf..c15b538 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -176,6 +176,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
@@ -198,11 +199,12 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.security.InvalidParameterException;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -919,8 +921,9 @@
         }
 
         void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final SimpleDateFormat dataFormat =
-                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+            final DateTimeFormatter formatter =
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                            .withZone(ZoneId.systemDefault());
 
             for (int i = 0; i < mEntries.length; ++i) {
                 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -931,7 +934,7 @@
                 pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":");
 
                 pw.print(prefix);
-                pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+                pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                         + " (timestamp=" + entry.mTimestamp + ")");
 
                 pw.print(prefix);
@@ -999,7 +1002,7 @@
         private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
 
         /**
-         * Entry size for non low-RAM devices.
+         * Entry size for low-RAM devices.
          *
          * <p>TODO: Consider to follow what other system services have been doing to manage
          * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
@@ -1015,7 +1018,7 @@
         }
 
         /**
-         * Backing store for the ring bugger.
+         * Backing store for the ring buffer.
          */
         private final Entry[] mEntries = new Entry[getEntrySize()];
 
@@ -1095,8 +1098,9 @@
         }
 
         void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final SimpleDateFormat dataFormat =
-                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+            final DateTimeFormatter formatter =
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                            .withZone(ZoneId.systemDefault());
 
             for (int i = 0; i < mEntries.length; ++i) {
                 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -1107,7 +1111,7 @@
                 pw.println("StartInput #" + entry.mSequenceNumber + ":");
 
                 pw.print(prefix);
-                pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+                pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                         + " (timestamp=" + entry.mTimestamp + ")"
                         + " reason="
                         + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
@@ -1149,6 +1153,9 @@
     private final SoftInputShowHideHistory mSoftInputShowHideHistory =
             new SoftInputShowHideHistory();
 
+    @NonNull
+    private final ImeTrackerService mImeTrackerService = new ImeTrackerService();
+
     class SettingsObserver extends ContentObserver {
         int mUserId;
         boolean mRegistered = false;
@@ -3405,13 +3412,11 @@
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            String packageName = null;
-            if (mCurEditorInfo != null) {
-                packageName = mCurEditorInfo.packageName;
-            }
-            statsToken = new ImeTracker.Token(packageName);
-            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
-                    reason);
+            // TODO(b/261565259): to avoid using null, add package name in ClientState
+            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
+            final int uid = mCurClient != null ? mCurClient.mUid : -1;
+            statsToken = ImeTracker.get().onRequestShow(packageName, uid,
+                    ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
         mShowRequested = true;
@@ -3461,7 +3466,7 @@
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
                 }
-                onShowHideSoftInputRequested(true /* show */, windowToken, reason);
+                onShowHideSoftInputRequested(true /* show */, windowToken, reason, statsToken);
             }
             mInputShown = true;
             return true;
@@ -3508,12 +3513,18 @@
             int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            String packageName = null;
-            if (mCurEditorInfo != null) {
-                packageName = mCurEditorInfo.packageName;
+            // TODO(b/261565259): to avoid using null, add package name in ClientState
+            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
+            final int uid;
+            if (mCurClient != null) {
+                uid = mCurClient.mUid;
+            } else if (mCurFocusedWindowClient != null) {
+                uid = mCurFocusedWindowClient.mUid;
+            } else {
+                uid = -1;
             }
-            statsToken = new ImeTracker.Token(packageName);
-            ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+            statsToken = ImeTracker.get().onRequestHide(packageName, uid,
+                    ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
         if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
@@ -3565,7 +3576,7 @@
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
                 }
-                onShowHideSoftInputRequested(false /* show */, windowToken, reason);
+                onShowHideSoftInputRequested(false /* show */, windowToken, reason, statsToken);
             }
             res = true;
         } else {
@@ -4781,6 +4792,7 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 return;
             }
             if (!setVisible) {
@@ -4846,7 +4858,7 @@
     /** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
     @GuardedBy("ImfLock.class")
     private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
-            @SoftInputShowHideReason int reason) {
+            @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
                         show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
@@ -4855,6 +4867,8 @@
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
                 info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
                 info.imeSurfaceParentName));
+
+        mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
     }
 
     @BinderThread
@@ -5994,6 +6008,9 @@
 
             p.println("  mSoftInputShowHideHistory:");
             mSoftInputShowHideHistory.dump(pw, "   ");
+
+            p.println("  mImeTrackerService#History:");
+            mImeTrackerService.dump(pw, "   ");
         }
 
         // Exit here for critical dump, as remaining sections require IPCs to other processes.
@@ -6584,6 +6601,12 @@
         return true;
     }
 
+    /** @hide */
+    @Override
+    public IImeTracker getImeTrackerService() {
+        return mImeTrackerService;
+    }
+
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
         private final InputMethodManagerService mImms;
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/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 50e1fca..e9ee750 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -370,6 +370,26 @@
             }
         }
 
+        @Override
+        public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+                        + "on captured content resize");
+            }
+            if (!isValidMediaProjection(mProjectionGrant)) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mProjectionGrant != null && mCallbackDelegate != null) {
+                    mCallbackDelegate.dispatchVisibilityChanged(mProjectionGrant, isVisible);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         @Override //Binder call
         public void addCallback(final IMediaProjectionWatcherCallback callback) {
             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
@@ -750,8 +770,9 @@
 
         public void dispatchResize(MediaProjection projection, int width, int height) {
             if (projection == null) {
-                Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
-                        + " Ignoring!");
+                Slog.e(TAG,
+                        "Tried to dispatch resize notification for a null media projection. "
+                                + "Ignoring!");
                 return;
             }
             synchronized (mLock) {
@@ -774,6 +795,36 @@
                 // is for passing along if recording is still ongoing or not.
             }
         }
+
+        public void dispatchVisibilityChanged(MediaProjection projection, boolean isVisible) {
+            if (projection == null) {
+                Slog.e(TAG,
+                        "Tried to dispatch visibility changed notification for a null media "
+                                + "projection. Ignoring!");
+                return;
+            }
+            synchronized (mLock) {
+                // TODO(b/249827847) Currently the service assumes there is only one projection
+                //  at once - need to find the callback for the given projection, when there are
+                //  multiple sessions.
+                for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+                    mHandler.post(() -> {
+                        try {
+                            // Notify every callback the client has registered for a particular
+                            // MediaProjection instance.
+                            callback.onCapturedContentVisibilityChanged(isVisible);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG,
+                                    "Failed to notify media projection has captured content "
+                                            + "visibility change to "
+                                            + isVisible, e);
+                        }
+                    });
+                }
+                // Do not need to notify watcher callback about visibility changes, since watcher
+                // callback is for passing along if recording is still ongoing or not.
+            }
+        }
     }
 
     private static final class WatcherStartCallback implements Runnable {
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..ff020eb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1455,7 +1455,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);
         }
@@ -4857,7 +4857,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 f683d0a..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;
@@ -191,6 +190,15 @@
     }
 
     @Override
+    public void activityRefreshed(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized (mGlobalLock) {
+            ActivityRecord.activityRefreshedLocked(token);
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    @Override
     public void activityTopResumedStateLost() {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
@@ -482,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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index aca5b61..bf4e25c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -736,7 +736,6 @@
      */
     private boolean mWillCloseOrEnterPip;
 
-    @VisibleForTesting
     final LetterboxUiController mLetterboxUiController;
 
     /**
@@ -6076,6 +6075,19 @@
         r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
     }
 
+    static void activityRefreshedLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+        ProtoLog.i(WM_DEBUG_STATES, "Refreshed activity: %s", r);
+        if (r == null) {
+            // In case the record on server side has been removed (e.g. destroy timeout)
+            // and the token could be null.
+            return;
+        }
+        if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
+            r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+        }
+    }
+
     static void splashScreenAttachedLocked(IBinder token) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         if (r == null) {
@@ -9151,6 +9163,8 @@
             } else {
                 scheduleConfigurationChanged(newMergedOverrideConfig);
             }
+            notifyDisplayCompatPolicyAboutConfigurationChange(
+                    mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
             return true;
         }
 
@@ -9219,11 +9233,24 @@
         } else {
             scheduleConfigurationChanged(newMergedOverrideConfig);
         }
+        notifyDisplayCompatPolicyAboutConfigurationChange(
+                mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
+
         stopFreezingScreenLocked(false);
 
         return true;
     }
 
+    private void notifyDisplayCompatPolicyAboutConfigurationChange(
+            Configuration newConfig, Configuration lastReportedConfig) {
+        if (mDisplayContent.mDisplayRotationCompatPolicy == null
+                || !shouldBeResumed(/* activeActivity */ null)) {
+            return;
+        }
+        mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+                this, newConfig, lastReportedConfig);
+    }
+
     /** Get process configuration, or global config if the process is not set. */
     private Configuration getProcessGlobalConfiguration() {
         return app != null ? app.getConfiguration() : mAtmService.getGlobalConfiguration();
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/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/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 8d5d0d5..af135b7 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -211,6 +211,7 @@
      * Stops recording on this DisplayContent, and updates the session details.
      */
     void stopRecording() {
+        unregisterListener();
         if (mRecordedSurface != null) {
             // Do not wait for the mirrored surface to be garbage collected, but clean up
             // immediately.
@@ -227,7 +228,7 @@
      * Ensure recording does not fall back to the display stack; ensure the recording is stopped
      * and the client notified by tearing down the virtual display.
      */
-    void stopMediaProjection() {
+    private void stopMediaProjection() {
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                 "Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
         if (mMediaProjectionManager != null) {
@@ -247,6 +248,16 @@
                 null, mDisplayContent.mWmService);
     }
 
+    private void unregisterListener() {
+        Task recordedTask = mRecordedWindowContainer != null
+                ? mRecordedWindowContainer.asTask() : null;
+        if (recordedTask == null || !isRecordingContentTask()) {
+            return;
+        }
+        recordedTask.unregisterWindowContainerListener(this);
+        mRecordedWindowContainer = null;
+    }
+
     /**
      * Start recording to this DisplayContent if it does not have its own content. Captures the
      * content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
@@ -301,6 +312,13 @@
         // Retrieve the size of the DisplayArea to mirror.
         updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
 
+        // Notify the client about the visibility of the mirrored region, now that we have begun
+        // capture.
+        if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+            mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                    mRecordedWindowContainer.asTask().isVisibleRequested());
+        }
+
         // No need to clean up. In SurfaceFlinger, parents hold references to their children. The
         // mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
         // holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
@@ -389,6 +407,7 @@
      */
     private void handleStartRecordingFailed() {
         final boolean shouldExitTaskRecording = isRecordingContentTask();
+        unregisterListener();
         clearContentRecordingSession();
         if (shouldExitTaskRecording) {
             // Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,12 +497,7 @@
                 "Recorded task is removed, so stop recording on display %d",
                 mDisplayContent.getDisplayId());
 
-        Task recordedTask = mRecordedWindowContainer != null
-                ? mRecordedWindowContainer.asTask() : null;
-        if (recordedTask == null || !isRecordingContentTask()) {
-            return;
-        }
-        recordedTask.unregisterWindowContainerListener(this);
+        unregisterListener();
         // Stop mirroring and teardown.
         clearContentRecordingSession();
         // Clean up the cached session first to ensure recording doesn't re-start, since
@@ -501,9 +515,20 @@
         mLastOrientation = mergedOverrideConfiguration.orientation;
     }
 
+    // WindowContainerListener
+    @Override
+    public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+        // Check still recording just to be safe.
+        if (isCurrentlyRecording() && mLastRecordedBounds != null) {
+            mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                    isVisibleRequested);
+        }
+    }
+
     @VisibleForTesting interface MediaProjectionManagerWrapper {
         void stopActiveProjection();
         void notifyActiveProjectionCapturedContentResized(int width, int height);
+        void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
     }
 
     private static final class RemoteMediaProjectionManagerWrapper implements
@@ -543,6 +568,23 @@
             }
         }
 
+        @Override
+        public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+            fetchMediaProjectionManager();
+            if (mIMediaProjectionManager == null) {
+                return;
+            }
+            try {
+                mIMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                        isVisible);
+            } catch (RemoteException e) {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Unable to tell MediaProjectionManagerService about visibility change on "
+                                + "the active projection: %s",
+                        e);
+            }
+        }
+
         private void fetchMediaProjectionManager() {
             if (mIMediaProjectionManager != null) {
                 return;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 044357b..82237bb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -449,6 +449,7 @@
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final DisplayPolicy mDisplayPolicy;
     private final DisplayRotation mDisplayRotation;
+    @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     DisplayFrames mDisplayFrames;
 
     private boolean mInTouchMode;
@@ -1178,6 +1179,13 @@
         onDisplayChanged(this);
         updateDisplayAreaOrganizers();
 
+        mDisplayRotationCompatPolicy =
+                // Not checking DeviceConfig value here to allow enabling via DeviceConfig
+                // without the need to restart the device.
+                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                            /* checkDeviceConfig */ false)
+                        ? new DisplayRotationCompatPolicy(this) : null;
+
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
         mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
@@ -2756,6 +2764,14 @@
             }
         }
 
+        if (mDisplayRotationCompatPolicy != null) {
+            int compatOrientation = mDisplayRotationCompatPolicy.getOrientation();
+            if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+                mLastOrientationSource = null;
+                return compatOrientation;
+            }
+        }
+
         final int orientation = super.getOrientation();
 
         if (!handlesOrientationChangeFromDescendant(orientation)) {
@@ -3318,6 +3334,10 @@
         // on the next traversal if it's removed from RootWindowContainer child list.
         getPendingTransaction().apply();
         mWmService.mWindowPlacerLocked.requestTraversal();
+
+        if (mDisplayRotationCompatPolicy != null) {
+            mDisplayRotationCompatPolicy.dispose();
+        }
     }
 
     /** Returns true if a removal action is still being deferred. */
@@ -6505,15 +6525,6 @@
     }
 
     /**
-     * The MediaProjection instance is torn down.
-     */
-    @VisibleForTesting void stopMediaProjection() {
-        if (mContentRecorder != null) {
-            mContentRecorder.stopMediaProjection();
-        }
-    }
-
-    /**
      * Sets the incoming recording session. Should only be used when starting to record on
      * this display; stopping recording is handled separately when the display is destroyed.
      *
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 34bdb7a..cf3a688 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1805,6 +1805,7 @@
             final int mHalfFoldSavedRotation;
             final boolean mInHalfFoldTransition;
             final DeviceStateController.FoldState mFoldState;
+            @Nullable final String mDisplayRotationCompatPolicySummary;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
                 mFromRotation = fromRotation;
@@ -1839,6 +1840,10 @@
                     mInHalfFoldTransition = false;
                     mFoldState = DeviceStateController.FoldState.UNKNOWN;
                 }
+                mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
+                        ? null
+                        : dc.mDisplayRotationCompatPolicy
+                                .getSummaryForDisplayRotationHistoryRecord();
             }
 
             void dump(String prefix, PrintWriter pw) {
@@ -1861,6 +1866,9 @@
                             + " mInHalfFoldTransition=" + mInHalfFoldTransition
                             + " mFoldState=" + mFoldState);
                 }
+                if (mDisplayRotationCompatPolicySummary != null) {
+                    pw.println(prefix + mDisplayRotationCompatPolicySummary);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
new file mode 100644
index 0000000..18c5c3b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -0,0 +1,433 @@
+/*
+ * 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.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controls camera compatibility treatment that handles orientation mismatch between camera
+ * buffers and an app window for a particular display that can lead to camera issues like sideways
+ * or stretched viewfinder.
+ *
+ * <p>This includes force rotation of fixed orientation activities connected to the camera.
+ *
+ * <p>The treatment is enabled for internal displays that have {@code ignoreOrientationRequest}
+ * display setting enabled and when {@code
+ * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+ */
+ // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
+final class DisplayRotationCompatPolicy {
+
+    // Delay for updating display rotation after Camera connection is closed. Needed to avoid
+    // rotation flickering when an app is flipping between front and rear cameras or when size
+    // compat mode is restarted.
+    // TODO(b/263114289): Consider associating this delay with a specific activity so that if
+    // the new non-camera activity started on top of the camer one we can rotate faster.
+    private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
+    // Delay for updating display rotation after Camera connection is opened. This delay is
+    // selected to be long enough to avoid conflicts with transitions on the app's side.
+    // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
+    // is flipping between front and rear cameras (in case requested orientation changes at
+    // runtime at the same time) or when size compat mode is restarted.
+    private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
+            CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
+    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+    // client process may not always report the event back to the server, such as process is
+    // crashed or got killed.
+    private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
+
+    private final DisplayContent mDisplayContent;
+    private final WindowManagerService mWmService;
+    private final CameraManager mCameraManager;
+    private final Handler mHandler;
+
+    // Bi-directional map between package names and active camera IDs since we need to 1) get a
+    // camera id by a package name when determining rotation; 2) get a package name by a camera id
+    // when camera connection is closed and we need to clean up our records.
+    @GuardedBy("this")
+    private final CameraIdPackageNameBiMap mCameraIdPackageBiMap = new CameraIdPackageNameBiMap();
+    @GuardedBy("this")
+    private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
+    @GuardedBy("this")
+    private final Set<String> mScheduledOrientationUpdateCameraIdSet = new ArraySet<>();
+
+    private final CameraManager.AvailabilityCallback mAvailabilityCallback =
+            new  CameraManager.AvailabilityCallback() {
+                @Override
+                public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
+                    notifyCameraOpened(cameraId, packageId);
+                }
+
+                @Override
+                public void onCameraClosed(@NonNull String cameraId) {
+                    notifyCameraClosed(cameraId);
+                }
+            };
+
+    @ScreenOrientation
+    private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
+
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
+        this(displayContent, displayContent.mWmService.mH);
+    }
+
+    @VisibleForTesting
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler) {
+        // This constructor is called from DisplayContent constructor. Don't use any fields in
+        // DisplayContent here since they aren't guaranteed to be set.
+        mHandler = handler;
+        mDisplayContent = displayContent;
+        mWmService = displayContent.mWmService;
+        mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
+        mCameraManager.registerAvailabilityCallback(
+                mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+    }
+
+    void dispose() {
+        mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
+    }
+
+    /**
+     * Determines orientation for Camera compatibility.
+     *
+     * <p>The goal of this function is to compute a orientation which would align orientations of
+     * portrait app window and natural orientation of the device and set opposite to natural
+     * orientation for a landscape app window. This is one of the strongest assumptions that apps
+     * make when they implement camera previews. Since app and natural display orientations aren't
+     * guaranteed to match, the rotation can cause letterboxing.
+     *
+     * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
+     * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
+     */
+    @ScreenOrientation
+    int getOrientation() {
+        mLastReportedOrientation = getOrientationInternal();
+        return mLastReportedOrientation;
+    }
+
+    @ScreenOrientation
+    private synchronized int getOrientationInternal() {
+        if (!isTreatmentEnabledForDisplay()) {
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                /* considerKeyguardState= */ true);
+        if (!isTreatmentEnabledForActivity(topActivity)) {
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        boolean isPortraitActivity =
+                topActivity.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+        boolean isNaturalDisplayOrientationPortrait =
+                mDisplayContent.getNaturalOrientation() == ORIENTATION_PORTRAIT;
+        // Rotate portrait-only activity in the natural orientation of the displays (and in the
+        // opposite to natural orientation for landscape-only) since many apps assume that those
+        // are aligned when they compute orientation of the preview.
+        // This means that even for a landscape-only activity and a device with landscape natural
+        // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
+        // natural orientation = portrait window = portait camera is the main wrong assumption
+        // that apps make when they implement camera previews so landscape windows need be
+        // rotated in the orientation oposite to the natural one even if it's portrait.
+        // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
+        // of the portrait and landscape orientation requests.
+        int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
+                || (!isPortraitActivity && !isNaturalDisplayOrientationPortrait)
+                        ? SCREEN_ORIENTATION_PORTRAIT
+                        : SCREEN_ORIENTATION_LANDSCAPE;
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is ignoring all orientation requests, camera is active "
+                        + "and the top activity is eligible for force rotation, return %s,"
+                        + "portrait activity: %b, is natural orientation portrait: %b.",
+                mDisplayContent.mDisplayId, screenOrientationToString(orientation),
+                isPortraitActivity, isNaturalDisplayOrientationPortrait);
+        return orientation;
+    }
+
+    /**
+     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+     * camera preview and can lead to sideways or stretching issues persisting even after force
+     * rotation.
+     */
+    void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
+            Configuration lastReportedConfig) {
+        if (!isTreatmentEnabledForDisplay()
+                || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+                || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+            return;
+        }
+        boolean cycleThroughStop = mWmService.mLetterboxConfiguration
+                .isCameraCompatRefreshCycleThroughStopEnabled();
+        try {
+            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
+            ProtoLog.v(WM_DEBUG_STATES,
+                    "Refershing activity for camera compatibility treatment, "
+                            + "activityRecord=%s", activity);
+            final ClientTransaction transaction = ClientTransaction.obtain(
+                    activity.app.getThread(), activity.token);
+            transaction.addCallback(
+                    RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+            transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+            activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+            mHandler.postDelayed(
+                    () -> onActivityRefreshed(activity),
+                    REFRESH_CALLBACK_TIMEOUT_MS);
+        } catch (RemoteException e) {
+            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+        }
+    }
+
+    void onActivityRefreshed(@NonNull ActivityRecord activity) {
+        activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+    }
+
+    String getSummaryForDisplayRotationHistoryRecord() {
+        String summaryIfEnabled = "";
+        if (isTreatmentEnabledForDisplay()) {
+            ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                    /* considerKeyguardState= */ true);
+            summaryIfEnabled =
+                    " mLastReportedOrientation="
+                            + screenOrientationToString(mLastReportedOrientation)
+                    + " topActivity="
+                            + (topActivity == null ? "null" : topActivity.shortComponentName)
+                    + " isTreatmentEnabledForActivity="
+                            + isTreatmentEnabledForActivity(topActivity)
+                    + " CameraIdPackageNameBiMap="
+                            + mCameraIdPackageBiMap.getSummaryForDisplayRotationHistoryRecord();
+        }
+        return "DisplayRotationCompatPolicy{"
+                + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay()
+                + summaryIfEnabled
+                + " }";
+    }
+
+    // Refreshing only when configuration changes after rotation.
+    private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
+            Configuration lastReportedConfig) {
+        return newConfig.windowConfiguration.getDisplayRotation()
+                        != lastReportedConfig.windowConfiguration.getDisplayRotation()
+                && isTreatmentEnabledForActivity(activity);
+    }
+
+    /**
+     * Whether camera compat treatment is enabled for the display.
+     *
+     * <p>Conditions that need to be met:
+     * <ul>
+     *     <li>{@code R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+     *     <li>Setting {@code ignoreOrientationRequest} is enabled for the display.
+     *     <li>Associated {@link DisplayContent} is for internal display. See b/225928882
+     *     that tracks supporting external displays in the future.
+     * </ul>
+     */
+    private boolean isTreatmentEnabledForDisplay() {
+        return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ true)
+                && mDisplayContent.getIgnoreOrientationRequest()
+                // TODO(b/225928882): Support camera compat rotation for external displays
+                && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
+    }
+
+    /**
+     * Whether camera compat treatment is applicable for the given activity.
+     *
+     * <p>Conditions that need to be met:
+     * <ul>
+     *     <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
+     *     <li>The activity is in fullscreen
+     *     <li>The activity has fixed orientation but not "locked" or "nosensor" one.
+     * </ul>
+     */
+    private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+        return activity != null && !activity.inMultiWindowMode()
+                && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
+                // "locked" and "nosensor" values are often used by camera apps that can't
+                // handle dynamic changes so we shouldn't force rotate them.
+                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
+                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
+                && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+    }
+
+    private synchronized void notifyCameraOpened(
+            @NonNull String cameraId, @NonNull String packageName) {
+        // If an activity is restarting or camera is flipping, the camera connection can be
+        // quickly closed and reopened.
+        mScheduledToBeRemovedCameraIdSet.remove(cameraId);
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is notified that Camera %s is open for package %s",
+                mDisplayContent.mDisplayId, cameraId, packageName);
+        // Some apps can’t handle configuration changes coming at the same time with Camera setup
+        // so delaying orientation update to accomadate for that.
+        mScheduledOrientationUpdateCameraIdSet.add(cameraId);
+        mHandler.postDelayed(
+                () ->  delayedUpdateOrientationWithWmLock(cameraId, packageName),
+                CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
+    }
+
+    private void updateOrientationWithWmLock() {
+        synchronized (mWmService.mGlobalLock) {
+            mDisplayContent.updateOrientation();
+        }
+    }
+
+    private void delayedUpdateOrientationWithWmLock(
+            @NonNull String cameraId, @NonNull String packageName) {
+        synchronized (this) {
+            if (!mScheduledOrientationUpdateCameraIdSet.remove(cameraId)) {
+                // Orientation update has happened already or was cancelled because
+                // camera was closed.
+                return;
+            }
+            mCameraIdPackageBiMap.put(packageName, cameraId);
+        }
+        updateOrientationWithWmLock();
+    }
+
+    private synchronized void notifyCameraClosed(@NonNull String cameraId) {
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+                mDisplayContent.mDisplayId, cameraId);
+        mScheduledToBeRemovedCameraIdSet.add(cameraId);
+        // No need to update orientation for this camera if it's already closed.
+        mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
+        scheduleRemoveCameraId(cameraId);
+    }
+
+    // Delay is needed to avoid rotation flickering when an app is flipping between front and
+    // rear cameras, when size compat mode is restarted or activity is being refreshed.
+    private void scheduleRemoveCameraId(@NonNull String cameraId) {
+        mHandler.postDelayed(
+                () -> removeCameraId(cameraId),
+                CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS);
+    }
+
+    private void removeCameraId(String cameraId) {
+        synchronized (this) {
+            if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
+                // Already reconnected to this camera, no need to clean up.
+                return;
+            }
+            if (isActivityForCameraIdRefreshing(cameraId)) {
+                ProtoLog.v(WM_DEBUG_ORIENTATION,
+                        "Display id=%d is notified that Camera %s is closed but activity is"
+                                + " still refreshing. Rescheduling an update.",
+                        mDisplayContent.mDisplayId, cameraId);
+                mScheduledToBeRemovedCameraIdSet.add(cameraId);
+                scheduleRemoveCameraId(cameraId);
+                return;
+            }
+            mCameraIdPackageBiMap.removeCameraId(cameraId);
+        }
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is notified that Camera %s is closed, updating rotation.",
+                mDisplayContent.mDisplayId, cameraId);
+        updateOrientationWithWmLock();
+    }
+
+    private boolean isActivityForCameraIdRefreshing(String cameraId) {
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                /* considerKeyguardState= */ true);
+        if (!isTreatmentEnabledForActivity(topActivity)) {
+            return false;
+        }
+        String activeCameraId = mCameraIdPackageBiMap.getCameraId(topActivity.packageName);
+        if (activeCameraId == null || activeCameraId != cameraId) {
+            return false;
+        }
+        return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+    }
+
+    private static class CameraIdPackageNameBiMap {
+
+        private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
+        private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
+
+        void put(String packageName, String cameraId) {
+            // Always using the last connected camera ID for the package even for the concurrent
+            // camera use case since we can't guess which camera is more important anyway.
+            removePackageName(packageName);
+            removeCameraId(cameraId);
+            mPackageToCameraIdMap.put(packageName, cameraId);
+            mCameraIdToPackageMap.put(cameraId, packageName);
+        }
+
+        boolean containsPackageName(String packageName) {
+            return mPackageToCameraIdMap.containsKey(packageName);
+        }
+
+        @Nullable
+        String getCameraId(String packageName) {
+            return mPackageToCameraIdMap.get(packageName);
+        }
+
+        void removeCameraId(String cameraId) {
+            String packageName = mCameraIdToPackageMap.get(cameraId);
+            if (packageName == null) {
+                return;
+            }
+            mPackageToCameraIdMap.remove(packageName, cameraId);
+            mCameraIdToPackageMap.remove(cameraId, packageName);
+        }
+
+        String getSummaryForDisplayRotationHistoryRecord() {
+            return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }";
+        }
+
+        private void removePackageName(String packageName) {
+            String cameraId = mPackageToCameraIdMap.get(packageName);
+            if (cameraId == null) {
+                return;
+            }
+            mPackageToCameraIdMap.remove(packageName, cameraId);
+            mCameraIdToPackageMap.remove(cameraId, packageName);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 3eca364..a7bf595f 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -191,6 +191,20 @@
     // Allows to enable letterboxing strategy for translucent activities ignoring flags.
     private boolean mTranslucentLetterboxingOverrideEnabled;
 
+    // Whether camera compatibility treatment is enabled.
+    // See DisplayRotationCompatPolicy for context.
+    private final boolean mIsCameraCompatTreatmentEnabled;
+
+    // Whether activity "refresh" in camera compatibility treatment is enabled.
+    // See RefreshCallbackItem for context.
+    private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
+
+    // Whether activity "refresh" in camera compatibility treatment should happen using the
+    // "stopped -> resumed" cycle rather than "paused -> resumed" cycle. Using "stop -> resumed"
+    // cycle by default due to higher success rate confirmed with app compatibility testing.
+    // See RefreshCallbackItem for context.
+    private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+
     LetterboxConfiguration(Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
@@ -241,6 +255,8 @@
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
         mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsEnabledForTranslucentActivities);
+        mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
+                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
@@ -947,9 +963,65 @@
                 isDeviceInTabletopMode, nextVerticalPosition);
     }
 
-    // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+    // TODO(b/262378106): Cache a runtime flag and implement
+    // DeviceConfig.OnPropertiesChangedListener
     static boolean isTranslucentLetterboxingAllowed() {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 "enable_translucent_activity_letterbox", false);
     }
+
+    /** Whether camera compatibility treatment is enabled. */
+    boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
+        return mIsCameraCompatTreatmentEnabled
+                && (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
+    }
+
+    // TODO(b/262977416): Cache a runtime flag and implement
+    // DeviceConfig.OnPropertiesChangedListener
+    private static boolean isCameraCompatTreatmentAllowed() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                "enable_camera_compat_treatment", false);
+    }
+
+    /** Whether camera compatibility refresh is enabled. */
+    boolean isCameraCompatRefreshEnabled() {
+        return mIsCameraCompatTreatmentRefreshEnabled;
+    }
+
+    /** Overrides whether camera compatibility treatment is enabled. */
+    void setCameraCompatRefreshEnabled(boolean enabled) {
+        mIsCameraCompatTreatmentRefreshEnabled = enabled;
+    }
+
+    /**
+     * Resets whether camera compatibility treatment is enabled to {@code true}.
+     */
+    void resetCameraCompatRefreshEnabled() {
+        mIsCameraCompatTreatmentRefreshEnabled = true;
+    }
+
+    /**
+     * Whether activity "refresh" in camera compatibility treatment should happen using the
+     * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+     */
+    boolean isCameraCompatRefreshCycleThroughStopEnabled() {
+        return mIsCameraCompatRefreshCycleThroughStopEnabled;
+    }
+
+    /**
+     * Overrides whether activity "refresh" in camera compatibility treatment should happen using
+     * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+     */
+    void setCameraCompatRefreshCycleThroughStopEnabled(boolean enabled) {
+        mIsCameraCompatRefreshCycleThroughStopEnabled = enabled;
+    }
+
+    /**
+     * Resets  whether activity "refresh" in camera compatibility treatment should happen using
+     * "stopped -> resumed" cycle rather than "paused -> resumed" cycle to {@code true}.
+     */
+    void resetCameraCompatRefreshCycleThroughStopEnabled() {
+        mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9cb94c6..fd7e082 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -80,6 +80,7 @@
 // SizeCompatTests and LetterboxTests but not all.
 // TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the
 // hierarchy in addition to ActivityRecord (Task, DisplayArea, ...).
+// TODO(b/263021211): Consider renaming to more generic CompatUIController.
 final class LetterboxUiController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
@@ -125,6 +126,11 @@
     @Nullable
     private Letterbox mLetterbox;
 
+    // Whether activity "refresh" was requested but not finished in
+    // ActivityRecord#activityResumedLocked following the camera compat force rotation in
+    // DisplayRotationCompatPolicy.
+    private boolean mIsRefreshAfterRotationRequested;
+
     LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
         mLetterboxConfiguration = wmService.mLetterboxConfiguration;
         // Given activityRecord may not be fully constructed since LetterboxUiController
@@ -147,6 +153,18 @@
         }
     }
 
+    /**
+     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
+     * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+     */
+    boolean isRefreshAfterRotationRequested() {
+        return mIsRefreshAfterRotationRequested;
+    }
+
+    void setIsRefreshAfterRotationRequested(boolean isRequested) {
+        mIsRefreshAfterRotationRequested = isRequested;
+    }
+
     boolean hasWallpaperBackgroundForLetterbox() {
         return mShowWallpaperForLetterboxBackground;
     }
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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ce41ae7..6737052 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1305,6 +1305,11 @@
         if (parent != null) {
             parent.onChildVisibleRequestedChanged(this);
         }
+
+        // Notify listeners about visibility change.
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            mListeners.get(i).onVisibleRequestedChanged(mVisibleRequested);
+        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainerListener.java b/services/core/java/com/android/server/wm/WindowContainerListener.java
index ac1fe17..c1ee254 100644
--- a/services/core/java/com/android/server/wm/WindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/WindowContainerListener.java
@@ -27,4 +27,13 @@
 
     /** Called when {@link WindowContainer#removeImmediately()} is invoked. */
     default void onRemoved() {}
+
+    /**
+     * Only invoked if the child successfully requested a visibility change.
+     *
+     * @param isVisibleRequested The current {@link WindowContainer#isVisibleRequested()} of this
+     *                           {@link WindowContainer} (not of the child).
+     * @see WindowContainer#onChildVisibleRequestedChanged(WindowContainer)
+     */
+    default void onVisibleRequestedChanged(boolean isVisibleRequested) { }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 1934b8c..e2c9c17 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -53,6 +53,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
@@ -821,54 +822,6 @@
         return 0;
     }
 
-    private int runSetLetterboxIsHorizontalReachabilityEnabled(PrintWriter pw)
-            throws RemoteException {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(enabled);
-        }
-        return 0;
-    }
-
-    private int runSetLetterboxIsVerticalReachabilityEnabled(PrintWriter pw)
-            throws RemoteException {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsVerticalReachabilityEnabled(enabled);
-        }
-        return 0;
-    }
-
     private int runSetLetterboxDefaultPositionForHorizontalReachability(PrintWriter pw)
             throws RemoteException {
         @LetterboxHorizontalReachabilityPosition final int position;
@@ -931,32 +884,13 @@
         return 0;
     }
 
-    private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsEducationEnabled(enabled);
-        }
-        return 0;
-    }
-
-    private int runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(PrintWriter pw)
+    private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
             throws RemoteException {
         String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: expected true, 1, false, 0, but got empty input.");
+            return -1;
+        }
         final boolean enabled;
         switch (arg) {
             case "true":
@@ -973,30 +907,7 @@
         }
 
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(enabled);
-        }
-        return 0;
-    }
-
-    private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+            setter.accept(enabled);
         }
         return 0;
     }
@@ -1039,10 +950,12 @@
                     runSetLetterboxVerticalPositionMultiplier(pw);
                     break;
                 case "--isHorizontalReachabilityEnabled":
-                    runSetLetterboxIsHorizontalReachabilityEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsHorizontalReachabilityEnabled);
                     break;
                 case "--isVerticalReachabilityEnabled":
-                    runSetLetterboxIsVerticalReachabilityEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsVerticalReachabilityEnabled);
                     break;
                 case "--defaultPositionForHorizontalReachability":
                     runSetLetterboxDefaultPositionForHorizontalReachability(pw);
@@ -1051,13 +964,23 @@
                     runSetLetterboxDefaultPositionForVerticalReachability(pw);
                     break;
                 case "--isEducationEnabled":
-                    runSetLetterboxIsEducationEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
                     break;
                 case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
-                    runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsSplitScreenAspectRatioForUnresizableAppsEnabled);
                     break;
                 case "--isTranslucentLetterboxingEnabled":
-                    runSetTranslucentLetterboxingEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setTranslucentLetterboxingOverrideEnabled);
+                    break;
+                case "--isCameraCompatRefreshEnabled":
+                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+                            .setCameraCompatRefreshEnabled(enabled));
+                    break;
+                case "--isCameraCompatRefreshCycleThroughStopEnabled":
+                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+                            .setCameraCompatRefreshCycleThroughStopEnabled(enabled));
                     break;
                 default:
                     getErrPrintWriter().println(
@@ -1104,27 +1027,34 @@
                         mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
                         break;
                     case "isHorizontalReachabilityEnabled":
-                        mLetterboxConfiguration.getIsHorizontalReachabilityEnabled();
+                        mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
                         break;
                     case "isVerticalReachabilityEnabled":
-                        mLetterboxConfiguration.getIsVerticalReachabilityEnabled();
+                        mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
                         break;
                     case "defaultPositionForHorizontalReachability":
-                        mLetterboxConfiguration.getDefaultPositionForHorizontalReachability();
+                        mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
                         break;
                     case "defaultPositionForVerticalReachability":
-                        mLetterboxConfiguration.getDefaultPositionForVerticalReachability();
+                        mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
                         break;
                     case "isEducationEnabled":
-                        mLetterboxConfiguration.getIsEducationEnabled();
+                        mLetterboxConfiguration.resetIsEducationEnabled();
                         break;
                     case "isSplitScreenAspectRatioForUnresizableAppsEnabled":
                         mLetterboxConfiguration
-                                .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+                                .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
                         break;
                     case "isTranslucentLetterboxingEnabled":
                         mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
                         break;
+                    case "isCameraCompatRefreshEnabled":
+                        mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+                        break;
+                    case "isCameraCompatRefreshCycleThroughStopEnabled":
+                        mLetterboxConfiguration
+                                .resetCameraCompatRefreshCycleThroughStopEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -1226,6 +1156,8 @@
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
             mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+            mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+            mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
         }
     }
 
@@ -1270,6 +1202,12 @@
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
                     + mLetterboxConfiguration
                             .getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+
+            pw.println("    Is activity \"refresh\" in camera compatibility treatment enabled: "
+                    + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
+            pw.println("    Refresh using \"stopped -> resumed\" cycle: "
+                    + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled());
+
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
                             mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1479,7 +1417,12 @@
         pw.println("        unresizable apps.");
         pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]");
         pw.println("        Whether letterboxing for translucent activities is enabled.");
-
+        pw.println("      --isCameraCompatRefreshEnabled [true|1|false|0]");
+        pw.println("        Whether camera compatibility refresh is enabled.");
+        pw.println("      --isCameraCompatRefreshCycleThroughStopEnabled [true|1|false|0]");
+        pw.println("        Whether activity \"refresh\" in camera compatibility treatment should");
+        pw.println("        happen using the \"stopped -> resumed\" cycle rather than");
+        pw.println("        \"paused -> resumed\" cycle.");
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
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..6b45ba2 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";
 
@@ -1512,6 +1515,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 +1770,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/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/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/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/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/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 92dd047..4ad8516 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.view.Display.INVALID_DISPLAY;
@@ -281,6 +280,64 @@
     }
 
     @Test
+    public void testStartRecording_notifiesCallback() {
+        // WHEN a recording is ongoing.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper)
+                .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+    }
+
+    @Test
+    public void testOnVisibleRequestedChanged_notifiesCallback() {
+        // WHEN a recording is ongoing.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // WHEN the child requests a visibility change.
+        boolean isVisibleRequested = true;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper, atLeastOnce())
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+        // WHEN the child requests a visibility change.
+        isVisibleRequested = false;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper)
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+    }
+
+    @Test
+    public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+        // WHEN a recording is not ongoing.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+        // WHEN the child requests a visibility change.
+        boolean isVisibleRequested = true;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is not notified.
+        verify(mMediaProjectionManagerWrapper, never())
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+        // WHEN the child requests a visibility change.
+        isVisibleRequested = false;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is not notified.
+        verify(mMediaProjectionManagerWrapper, never())
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+    }
+
+    @Test
     public void testPauseRecording_pausesRecording() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
new file mode 100644
index 0000000..d1234e3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -0,0 +1,428 @@
+/*
+ * 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.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.Surface.Rotation;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link DisplayRotationCompatPolicy}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:DisplayRotationCompatPolicyTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
+
+    private static final String TEST_PACKAGE_1 = "com.test.package.one";
+    private static final String TEST_PACKAGE_2 = "com.test.package.two";
+    private static final String CAMERA_ID_1 = "camera-1";
+    private static final String CAMERA_ID_2 = "camera-2";
+
+    private CameraManager mMockCameraManager;
+    private Handler mMockHandler;
+    private LetterboxConfiguration mLetterboxConfiguration;
+
+    private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
+    private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
+
+    private ActivityRecord mActivity;
+    private Task mTask;
+
+    @Before
+    public void setUp() throws Exception {
+        mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+        spyOn(mLetterboxConfiguration);
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ anyBoolean()))
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(true);
+
+        mMockCameraManager = mock(CameraManager.class);
+        doAnswer(invocation -> {
+            mCameraAvailabilityCallback = invocation.getArgument(1);
+            return null;
+        }).when(mMockCameraManager).registerAvailabilityCallback(
+                any(Executor.class), any(CameraManager.AvailabilityCallback.class));
+
+        spyOn(mContext);
+        when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
+
+        spyOn(mDisplayContent);
+
+        mDisplayContent.setIgnoreOrientationRequest(true);
+
+        mMockHandler = mock(Handler.class);
+
+        when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+                invocation -> {
+                    ((Runnable) invocation.getArgument(0)).run();
+                    return null;
+                });
+        mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
+                mDisplayContent, mMockHandler);
+    }
+
+    @Test
+    public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ anyBoolean()))
+                .thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ true))
+                .thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
+        mActivity.getTask().reparent(organizer.mPrimary, WindowContainer.POSITION_TOP,
+                false /* moveParents */, "test" /* reason */);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(mActivity.inMultiWindowMode());
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testOrientationUnspecified_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testOrientationLocked_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_LOCKED);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testOrientationNoSensor_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_NOSENSOR);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testIgnoreOrientationRequestIsFalse_noForceRotationOrRefresh() throws Exception {
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testDisplayNotInternal_noForceRotationOrRefresh() throws Exception {
+        Display display = mDisplayContent.getDisplay();
+        spyOn(display);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        when(display.getType()).thenReturn(Display.TYPE_EXTERNAL);
+        assertNoForceRotationOrRefresh();
+
+        when(display.getType()).thenReturn(Display.TYPE_WIFI);
+        assertNoForceRotationOrRefresh();
+
+        when(display.getType()).thenReturn(Display.TYPE_OVERLAY);
+        assertNoForceRotationOrRefresh();
+
+        when(display.getType()).thenReturn(Display.TYPE_VIRTUAL);
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testNoCameraConnection_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testCameraReconnected_forceRotationAndRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_PORTRAIT);
+        assertActivityRefreshRequested(/* refreshRequested */ true);
+    }
+
+    @Test
+    public void testReconnectedToDifferentCamera_forceRotationAndRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_PORTRAIT);
+        assertActivityRefreshRequested(/* refreshRequested */ true);
+    }
+
+    @Test
+    public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Test
+    public void testCameraOpenedForDifferentPackage_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testGetOrientation_portraitActivity_portraitNaturalOrientation_returnPortrait() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+                /* naturalOrientation */ ORIENTATION_PORTRAIT,
+                /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    @Test
+    public void testGetOrientation_portraitActivity_landscapeNaturalOrientation_returnLandscape() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+                /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+                /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+    }
+
+    @Test
+    public void testGetOrientation_landscapeActivity_portraitNaturalOrientation_returnLandscape() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+                /* naturalOrientation */ ORIENTATION_PORTRAIT,
+                /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+    }
+
+    @Test
+    public void testGetOrientation_landscapeActivity_landscapeNaturalOrientation_returnPortrait() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+                /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+                /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    private void testGetOrientationForActivityAndNaturalOrientations(
+            @ScreenOrientation int activityOrientation,
+            @Orientation int naturalOrientation,
+            @ScreenOrientation int expectedOrientation) {
+        configureActivityAndDisplay(activityOrientation, naturalOrientation);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                expectedOrientation);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertActivityRefreshRequested(/* refreshRequested */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
+            throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
+
+        assertActivityRefreshRequested(/* refreshRequested */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    private void configureActivity(@ScreenOrientation int activityOrientation) {
+        configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
+    }
+
+    private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
+            @Orientation int naturalOrientation) {
+
+        mTask = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent)
+                .build();
+
+        mActivity = new ActivityBuilder(mAtm)
+                .setComponent(new ComponentName(TEST_PACKAGE_1, ".TestActivity"))
+                .setScreenOrientation(activityOrientation)
+                .setTask(mTask)
+                .build();
+
+        spyOn(mActivity.mAtmService.getLifecycleManager());
+        spyOn(mActivity.mLetterboxUiController);
+
+        doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
+        doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+        assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested,
+                boolean cycleThroughStop) throws Exception {
+        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+                .setIsRefreshAfterRotationRequested(true);
+
+        final ClientTransaction transaction = ClientTransaction.obtain(
+                mActivity.app.getThread(), mActivity.token);
+        transaction.addCallback(RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+
+        verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+                .scheduleTransaction(eq(transaction));
+    }
+
+    private void assertNoForceRotationOrRefresh() throws Exception {
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+        assertActivityRefreshRequested(/* refreshRequested */ false);
+    }
+
+    private void callOnActivityConfigurationChanging(
+            ActivityRecord activity, boolean isDisplayRotationChanging) {
+        mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+                /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
+                /* newConfig */ createConfigurationWithDisplayRotation(
+                        isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
+    }
+
+    private static Configuration createConfigurationWithDisplayRotation(@Rotation int rotation) {
+        final Configuration config = new Configuration();
+        config.windowConfiguration.setDisplayRotation(rotation);
+        return config;
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index d583e89..1a1ca54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -56,6 +56,8 @@
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -660,6 +662,111 @@
     }
 
     @Test
+    public void testSetVisibleRequested() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        assertThat(root.isVisibleRequested()).isFalse();
+        final TestWindowContainerListener listener = new TestWindowContainerListener();
+        root.registerWindowContainerListener(listener);
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(root.isVisibleRequested()).isFalse();
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(root.isVisibleRequested()).isTrue();
+        assertThat(listener.mIsVisibleRequested).isTrue();
+    }
+
+    @Test
+    public void testSetVisibleRequested_childRequestsVisible() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+        assertThat(child1.isVisibleRequested()).isFalse();
+        final TestWindowContainerListener listener = new TestWindowContainerListener();
+        root.registerWindowContainerListener(listener);
+
+        // Hidden root and child request hidden.
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(listener.mIsVisibleRequested).isFalse();
+        assertThat(child1.isVisibleRequested()).isFalse();
+
+        // Child requests to be visible, so child and root request visible.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(root.isVisibleRequested()).isTrue();
+        assertThat(listener.mIsVisibleRequested).isTrue();
+        assertThat(child1.isVisibleRequested()).isTrue();
+        // Visible request didn't change.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isFalse();
+        verify(root, times(2)).onChildVisibleRequestedChanged(child1);
+    }
+
+    @Test
+    public void testSetVisibleRequested_childRequestsHidden() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+        assertThat(child1.isVisibleRequested()).isFalse();
+        final TestWindowContainerListener listener = new TestWindowContainerListener();
+        root.registerWindowContainerListener(listener);
+
+        // Root and child requests visible.
+        assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(listener.mIsVisibleRequested).isTrue();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(child1.isVisibleRequested()).isTrue();
+
+        // Child requests hidden, so child and root request hidden.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isTrue();
+        assertThat(root.isVisibleRequested()).isFalse();
+        assertThat(listener.mIsVisibleRequested).isFalse();
+        assertThat(child1.isVisibleRequested()).isFalse();
+        // Visible request didn't change.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        verify(root, times(3)).onChildVisibleRequestedChanged(child1);
+    }
+
+    @Test
+    public void testOnChildVisibleRequestedChanged_bothVisible() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        // Child and root request visible.
+        assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+        // Visible request already updated on root when child requested.
+        assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+    }
+
+    @Test
+    public void testOnChildVisibleRequestedChanged_childVisible() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+        // Visible request already updated on root when child requested.
+        assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+    }
+
+    @Test
+    public void testOnChildVisibleRequestedChanged_childHidden() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+
+        // Visible request did not change.
+        assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+    }
+
+    @Test
     public void testSetOrientation() {
         final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).build());
         final TestWindowContainer child = spy(root.addChildWindow());
@@ -1656,6 +1763,7 @@
     private static class TestWindowContainerListener implements WindowContainerListener {
         private Configuration mConfiguration = new Configuration();
         private DisplayContent mDisplayContent;
+        private boolean mIsVisibleRequested;
 
         @Override
         public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
@@ -1666,5 +1774,10 @@
         public void onDisplayChanged(DisplayContent dc) {
             mDisplayContent = dc;
         }
+
+        @Override
+        public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+            mIsVisibleRequested = isVisibleRequested;
+        }
     }
 }
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/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index fccdf76..c7cc1bd 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -27,7 +27,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -58,6 +57,9 @@
     /** Direct the audio stream through the device's speakerphone. */
     public static final int ROUTE_SPEAKER       = 0x00000008;
 
+    /** Direct the audio stream through another device. */
+    public static final int ROUTE_STREAMING     = 0x00000010;
+
     /**
      * Direct the audio stream through the device's earpiece or wired headset if one is
      * connected.
@@ -70,7 +72,7 @@
      * @hide
      **/
     public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
-            ROUTE_SPEAKER;
+            ROUTE_SPEAKER | ROUTE_STREAMING;
 
     private final boolean isMuted;
     private final int route;
@@ -189,7 +191,11 @@
      */
     @CallAudioRoute
     public int getSupportedRouteMask() {
-        return supportedRouteMask;
+        if (route == ROUTE_STREAMING) {
+            return ROUTE_STREAMING;
+        } else {
+            return supportedRouteMask;
+        }
     }
 
     /**
@@ -232,6 +238,9 @@
         if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
             listAppend(buffer, "SPEAKER");
         }
+        if ((route & ROUTE_STREAMING) == ROUTE_STREAMING) {
+            listAppend(buffer, "STREAMING");
+        }
 
         return buffer.toString();
     }
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 3bda6f4..867bcc7 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -191,6 +191,38 @@
     }
 
     /**
+     * Request start a call streaming session. On receiving valid request, telecom will bind to
+     * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
+     * call streaming sender can perform streaming local device audio to another remote device and
+     * control the call during streaming.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                 will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                 of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 rejected the incoming call.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 reject the incoming call.  A {@link CallException} will be passed that
+     *                 details why the operation failed.
+     */
+    public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.startCallStreaming(mCallId,
+                        new CallControlResultReceiver("startCallStreaming", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
      * This method should be called after
      * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
      * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
diff --git a/telecomm/java/android/telecom/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java
index a26291f..fd7e101 100644
--- a/telecomm/java/android/telecom/CallEventCallback.java
+++ b/telecomm/java/android/telecom/CallEventCallback.java
@@ -100,4 +100,22 @@
      * @param callAudioState that is currently being used
      */
     void onCallAudioStateChanged(@NonNull CallAudioState callAudioState);
+
+    /**
+     * Telecom is informing the client to set the call in streaming.
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can stream the
+     *                     call on their end, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+     *                     should be called with {@link Boolean#FALSE}.
+     */
+    void onCallStreamingStarted(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client user requested call streaming but the stream can't be
+     * started.
+     *
+     * @param reason Code to indicate the reason of this failure
+     */
+    void onCallStreamingFailed(@CallStreamingService.StreamingFailedReason int reason);
 }
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
new file mode 100644
index 0000000..affa6b6
--- /dev/null
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.telecom.ICallStreamingService;
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This service is implemented by an app that wishes to provide functionality for a general call
+ * streaming sender for voip calls.
+ *
+ * TODO: add doc of how to be the general streaming sender
+ *
+ */
+public abstract class CallStreamingService extends Service {
+    /**
+     * The {@link android.content.Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+
+    private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1;
+    private static final int MSG_CALL_STREAMING_STARTED = 2;
+    private static final int MSG_CALL_STREAMING_STOPPED = 3;
+    private static final int MSG_CALL_STREAMING_CHANGED_CHANGED = 4;
+
+    /** Default Handler used to consolidate binder method calls onto a single thread. */
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            if (mStreamingCallAdapter == null && msg.what != MSG_SET_STREAMING_CALL_ADAPTER) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_SET_STREAMING_CALL_ADAPTER:
+                    mStreamingCallAdapter = new StreamingCallAdapter(
+                            (IStreamingCallAdapter) msg.obj);
+                    break;
+                case MSG_CALL_STREAMING_STARTED:
+                    mCall = (StreamingCall) msg.obj;
+                    mCall.setAdapter(mStreamingCallAdapter);
+                    CallStreamingService.this.onCallStreamingStarted(mCall);
+                    break;
+                case MSG_CALL_STREAMING_STOPPED:
+                    mCall = null;
+                    mStreamingCallAdapter = null;
+                    CallStreamingService.this.onCallStreamingStopped();
+                    break;
+                case MSG_CALL_STREAMING_CHANGED_CHANGED:
+                    int state = (int) msg.obj;
+                    mCall.setStreamingState(state);
+                    CallStreamingService.this.onCallStreamingStateChanged(state);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Nullable
+    @Override
+    public IBinder onBind(@NonNull Intent intent) {
+        return new CallStreamingServiceBinder();
+    }
+
+    /** Manages the binder calls so that the implementor does not need to deal with it. */
+    private final class CallStreamingServiceBinder extends ICallStreamingService.Stub {
+        @Override
+        public void setStreamingCallAdapter(IStreamingCallAdapter streamingCallAdapter)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, mStreamingCallAdapter)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onCallStreamingStarted(StreamingCall call) throws RemoteException {
+            mHandler.obtainMessage(MSG_CALL_STREAMING_STARTED, call).sendToTarget();
+        }
+
+        @Override
+        public void onCallStreamingStopped() throws RemoteException {
+            mHandler.obtainMessage(MSG_CALL_STREAMING_STOPPED).sendToTarget();
+        }
+
+        @Override
+        public void onCallStreamingStateChanged(int state) throws RemoteException {
+            mHandler.obtainMessage(MSG_CALL_STREAMING_CHANGED_CHANGED, state).sendToTarget();
+        }
+    }
+
+    /**
+     * Call streaming request reject reason used with
+     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+     * call streaming request because there's an ongoing streaming call on this device.
+     */
+    public static final int STREAMING_FAILED_ALREADY_STREAMING = 1;
+
+    /**
+     * Call streaming request reject reason used with
+     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+     * call streaming request because telecom can't find existing general streaming sender on this
+     * device.
+     */
+    public static final int STREAMING_FAILED_NO_SENDER = 2;
+
+    /**
+     * Call streaming request reject reason used with
+     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+     * call streaming request because telecom can't bind to the general streaming sender app.
+     */
+    public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3;
+
+    private StreamingCallAdapter mStreamingCallAdapter;
+    private StreamingCall mCall;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"STREAMING_FAILED"},
+            value = {
+                    STREAMING_FAILED_ALREADY_STREAMING,
+                    STREAMING_FAILED_NO_SENDER,
+                    STREAMING_FAILED_SENDER_BINDING_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StreamingFailedReason {};
+
+    /**
+     * Called when a {@code StreamingCall} has been added to this call streaming session. The call
+     * streaming sender should start to intercept the device audio using audio records and audio
+     * tracks from Audio frameworks.
+     *
+     * @param call a newly added {@code StreamingCall}.
+     */
+    public void onCallStreamingStarted(@NonNull StreamingCall call) {
+    }
+
+    /**
+     * Called when a current {@code StreamingCall} has been removed from this call streaming
+     * session. The call streaming sender should notify the streaming receiver that the call is
+     * stopped streaming and stop the device audio interception.
+     */
+    public void onCallStreamingStopped() {
+    }
+
+    /**
+     * Called when the streaming state of current {@code StreamingCall} changed. General streaming
+     * sender usually get notified of the holding/unholding from the original owner voip app of the
+     * call.
+     */
+    public void onCallStreamingStateChanged(@StreamingCall.StreamingCallState int state) {
+    }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 047ab3a..b8c056e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -437,7 +437,15 @@
      */
     public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 0x40000;
 
-    /* NEXT CAPABILITY: [0x80000, 0x100000, 0x200000] */
+    /**
+     * Flag indicating that this voip app {@link PhoneAccount} supports the call streaming session
+     * to stream call audio to another remote device via streaming app.
+     *
+     * @see #getCapabilities
+     */
+    public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 0x80000;
+
+    /* NEXT CAPABILITY: [0x100000, 0x200000, 0x400000] */
 
     /**
      * URI scheme for telephone number URIs.
diff --git a/telecomm/java/android/telecom/StreamingCall.aidl b/telecomm/java/android/telecom/StreamingCall.aidl
new file mode 100644
index 0000000..d286658
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable StreamingCall;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
new file mode 100644
index 0000000..985cccc
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -0,0 +1,178 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a voip call requested to stream to another device that the general streaming sender
+ * app should present to the receiver.
+ */
+public final class StreamingCall implements Parcelable {
+    /**
+     * The state of a {@code StreamingCall} when newly created. General streaming sender should
+     * continuously stream call audio to the sender device as long as the {@code StreamingCall} is
+     * in this state.
+     */
+    public static final int STATE_STREAMING = 1;
+
+    /**
+     * The state of a {@code StreamingCall} when in a holding state.
+     */
+    public static final int STATE_HOLDING = 2;
+
+    /**
+     * The state of a {@code StreamingCall} when it's either disconnected or pulled back to the
+     * original device.
+     */
+    public static final int STATE_DISCONNECTED = 3;
+
+    private StreamingCall(@NonNull Parcel in) {
+        mComponentName = in.readParcelable(ComponentName.class.getClassLoader());
+        mDisplayName = in.readString16NoHelper();
+        mAddress = in.readParcelable(Uri.class.getClassLoader());
+        mExtras = in.readBundle();
+        mState = in.readInt();
+    }
+
+    @NonNull
+    public static final Creator<StreamingCall> CREATOR = new Creator<>() {
+        @Override
+        public StreamingCall createFromParcel(@NonNull Parcel in) {
+            return new StreamingCall(in);
+        }
+
+        @Override
+        public StreamingCall[] newArray(int size) {
+            return new StreamingCall[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mComponentName, flags);
+        dest.writeString16NoHelper(mDisplayName);
+        dest.writeParcelable(mAddress, flags);
+        dest.writeBundle(mExtras);
+        dest.writeInt(mState);
+    }
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "STATE_" },
+            value = {
+                    STATE_STREAMING,
+                    STATE_HOLDING,
+                    STATE_DISCONNECTED
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StreamingCallState {}
+
+    private final ComponentName mComponentName;
+    private final String mDisplayName;
+    private final Uri mAddress;
+    private final Bundle mExtras;
+    @StreamingCallState
+    private int mState;
+    private StreamingCallAdapter mAdapter = null;
+
+    public StreamingCall(@NonNull ComponentName componentName, @NonNull String displayName,
+            @NonNull Uri address, @NonNull Bundle extras) {
+        mComponentName = componentName;
+        mDisplayName = displayName;
+        mAddress = address;
+        mExtras = extras;
+        mState = STATE_STREAMING;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAdapter(StreamingCallAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * @return The {@link ComponentName} to identify the original voip app of this
+     * {@code StreamingCall}. General streaming sender app can use this to query necessary
+     * information (app icon etc.) in order to present notification of the streaming call on the
+     * receiver side.
+     */
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * @return The display name that the general streaming sender app can use this to present the
+     * {@code StreamingCall} to the receiver side.
+     */
+    @NonNull
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    /**
+     * @return The address (e.g., phone number) to which the {@code StreamingCall} is currently
+     * connected.
+     */
+    @NonNull
+    public Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * @return The state of this {@code StreamingCall}.
+     */
+    @StreamingCallState
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * @return The extra info the general streaming app need to stream the call from voip app or
+     * D2DI sdk.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Sets the state of this {@code StreamingCall}. The general streaming sender app can use this
+     * to request holding, unholding and disconnecting this {@code StreamingCall}.
+     * @param state The current streaming state of the call.
+     */
+    public void setStreamingState(@StreamingCallState int state) {
+        mAdapter.setStreamingState(state);
+    }
+}
diff --git a/telecomm/java/android/telecom/StreamingCallAdapter.java b/telecomm/java/android/telecom/StreamingCallAdapter.java
new file mode 100644
index 0000000..bd8727d
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCallAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.telecom;
+
+import android.os.RemoteException;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Receives commands from {@link CallStreamingService} implementations which should be executed by
+ * Telecom. When Telecom binds to a {@link CallStreamingService}, an instance of this class is given
+ * to the general streaming app through which it can manipulate the streaming calls. Whe the general
+ * streaming app is notified of new ongoing streaming calls, it can execute
+ * {@link StreamingCall#setStreamingState(int)} for the ongoing streaming calls the user on the
+ * receiver side would like to hold, unhold and disconnect.
+ *
+ * @hide
+ */
+public final class StreamingCallAdapter {
+    private final IStreamingCallAdapter mAdapter;
+
+    /**
+     * {@hide}
+     */
+    public StreamingCallAdapter(IStreamingCallAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Instruct telecom to change the state of the streaming call.
+     *
+     * @param state The streaming state to set
+     */
+    public void setStreamingState(@StreamingCall.StreamingCallState int state) {
+        try {
+            mAdapter.setStreamingState(state);
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 682dba1..16816ff 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -139,6 +139,7 @@
         private static final String ON_ANSWER = "onAnswer";
         private static final String ON_REJECT = "onReject";
         private static final String ON_DISCONNECT = "onDisconnect";
+        private static final String ON_STREAMING_STARTED = "onStreamingStarted";
 
         private void handleCallEventCallback(String action, String callId, int code,
                 ResultReceiver ackResultReceiver) {
@@ -174,6 +175,9 @@
                             case ON_ANSWER:
                                 callback.onAnswer(code, outcomeReceiverWrapper);
                                 break;
+                            case ON_STREAMING_STARTED:
+                                callback.onCallStreamingStarted(outcomeReceiverWrapper);
+                                break;
                         }
                     });
                 } catch (Exception e) {
@@ -249,9 +253,7 @@
             if (call != null) {
                 CallEventCallback callback = call.getCallEventCallback();
                 Executor executor = call.getExecutor();
-                executor.execute(() -> {
-                    callback.onCallAudioStateChanged(callAudioState);
-                });
+                executor.execute(() -> callback.onCallAudioStateChanged(callAudioState));
             }
         }
 
@@ -259,5 +261,23 @@
         public void removeCallFromTransactionalServiceWrapper(String callId) {
             untrackCall(callId);
         }
+
+        @Override
+        public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_STREAMING_STARTED, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onCallStreamingFailed(String callId, int reason) {
+            Log.i(TAG, TextUtils.formatSimple("onCallAudioStateChanged: callId=[%s], reason=[%s]",
+                    callId, reason));
+            // lookup the callEventCallback associated with the particular call
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+            if (call != null) {
+                CallEventCallback callback = call.getCallEventCallback();
+                Executor executor = call.getExecutor();
+                executor.execute(() -> callback.onCallStreamingFailed(reason));
+            }
+        }
     };
 }
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index bf68c5e..dc0aeac 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -28,4 +28,5 @@
     void setInactive(String callId, in ResultReceiver callback);
     void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
     void rejectCall(String callId, in ResultReceiver callback);
+    void startCallStreaming(String callId, in ResultReceiver callback);
 }
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
index 7f5825a..c45ef97 100644
--- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -35,6 +35,9 @@
     void onReject(String callId, in ResultReceiver callback);
     void onDisconnect(String callId, in ResultReceiver callback);
     void onCallAudioStateChanged(String callId, in CallAudioState callAudioState);
+    // Streaming related. Client registered call streaming capabilities should override
+    void onCallStreamingStarted(String callId, in ResultReceiver callback);
+    void onCallStreamingFailed(String callId, int reason);
     // hidden methods that help with cleanup
     void removeCallFromTransactionalServiceWrapper(String callId);
 }
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
new file mode 100644
index 0000000..6d53fd2
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.telecom;
+
+import android.telecom.StreamingCall;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Internal remote interface for call streaming services.
+ *
+ * @see android.telecom.CallStreamingService
+ *
+ * {@hide}
+ */
+oneway interface ICallStreamingService {
+    void setStreamingCallAdapter(in IStreamingCallAdapter streamingCallAdapter);
+    void onCallStreamingStarted(in StreamingCall call);
+    void onCallStreamingStopped();
+    void onCallStreamingStateChanged(int state);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
new file mode 100644
index 0000000..51424a6
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.telecom;
+
+/**
+ * Internal remote callback interface for call streaming services.
+ *
+ * @see android.telecom.StreamingCallAdapter
+ *
+ * {@hide}
+ */
+oneway interface IStreamingCallAdapter {
+    void setStreamingState(int state);
+}
\ No newline at end of file
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);
         }
     }