Merge "Notify IMS Call info via CallAttributesListener"
diff --git a/Android.bp b/Android.bp
index 4e7eba2..3d25bc1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -100,7 +100,7 @@
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
         ":android.hardware.keymaster-V4-java-source",
-        ":android.hardware.security.keymint-V2-java-source",
+        ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
         ":android.hardware.tv.tuner-V1-java-source",
         ":android.security.apc-java-source",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e178583..a48ce0c 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -15,6 +15,22 @@
       ]
     }
   ],
+  "presubmit-pm": [
+    {
+      "name": "PackageManagerServiceServerTests",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ],
   "presubmit": [
     {
       "name": "ManagedProvisioningTests",
@@ -167,6 +183,20 @@
          "exclude-annotation": "org.junit.Ignore"
        }
      ]
+   },
+   {
+     "name": "PackageManagerServiceServerTests",
+     "options": [
+       {
+         "include-annotation": "android.platform.test.annotations.Presubmit"
+       },
+       {
+         "exclude-annotation": "androidx.test.filters.FlakyTest"
+       },
+       {
+         "exclude-annotation": "org.junit.Ignore"
+       }
+     ]
    }
  ]
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index d281da0..a3390b7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -30,6 +30,25 @@
  */
 interface IJobCallback {
     /**
+     * Immediate callback to the system after sending a data transfer download progress request
+     * signal; used to quickly detect ANR.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param workId Unique integer used to identify a specific work item.
+     * @param transferredBytes How much data has been downloaded, in bytes.
+     */
+    void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+            long transferredBytes);
+    /**
+     * Immediate callback to the system after sending a data transfer upload progress request
+     * signal; used to quickly detect ANR.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param workId Unique integer used to identify a specific work item.
+     * @param transferredBytes How much data has been uploaded, in bytes.
+     */
+    void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId, long transferredBytes);
+    /**
      * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
      *
      * @param jobId Unique integer used to identify this job.
@@ -65,4 +84,24 @@
      */
     @UnsupportedAppUsage
     void jobFinished(int jobId, boolean reschedule);
+    /*
+     * Inform JobScheduler of a change in the estimated transfer payload.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param item The particular JobWorkItem this progress is associated with, if any.
+     * @param downloadBytes How many bytes the app expects to download.
+     * @param uploadBytes How many bytes the app expects to upload.
+     */
+    void updateEstimatedNetworkBytes(int jobId, in JobWorkItem item,
+            long downloadBytes, long uploadBytes);
+    /*
+     * Update JobScheduler of how much data the job has successfully transferred.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param item The particular JobWorkItem this progress is associated with, if any.
+     * @param transferredDownloadBytes The number of bytes that have successfully been downloaded.
+     * @param transferredUploadBytes The number of bytes that have successfully been uploaded.
+     */
+    void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
+            long transferredDownloadBytes, long transferredUploadBytes);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
index 22ad252..2bb82bd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
@@ -17,6 +17,7 @@
 package android.app.job;
 
 import android.app.job.JobParameters;
+import android.app.job.JobWorkItem;
 
 /**
  * Interface that the framework uses to communicate with application code that implements a
@@ -31,4 +32,8 @@
     /** Stop execution of application's job. */
     @UnsupportedAppUsage
     void stopJob(in JobParameters jobParams);
+    /** Update JS of how much data has been downloaded. */
+    void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
+    /** Update JS of how much data has been uploaded. */
+    void getTransferredUploadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index e0db3a6..76f71a2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -23,8 +23,11 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UserIdInt;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ClipData;
 import android.content.Context;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 
@@ -94,6 +97,16 @@
  */
 @SystemService(Context.JOB_SCHEDULER_SERVICE)
 public abstract class JobScheduler {
+    /**
+     * Whether to throw an exception when an app doesn't properly implement all the necessary
+     * data transfer APIs.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION = 255371817L;
+
     /** @hide */
     @IntDef(prefix = { "RESULT_" }, value = {
             RESULT_FAILURE,
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index d184d44..bad641c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -16,7 +16,13 @@
 
 package android.app.job;
 
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Service;
+import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.IBinder;
 
@@ -72,6 +78,28 @@
                 public boolean onStopJob(JobParameters params) {
                     return JobService.this.onStopJob(params);
                 }
+
+                @Override
+                @BytesLong
+                public long getTransferredDownloadBytes(@NonNull JobParameters params,
+                        @Nullable JobWorkItem item) {
+                    if (item == null) {
+                        return JobService.this.getTransferredDownloadBytes();
+                    } else {
+                        return JobService.this.getTransferredDownloadBytes(item);
+                    }
+                }
+
+                @Override
+                @BytesLong
+                public long getTransferredUploadBytes(@NonNull JobParameters params,
+                        @Nullable JobWorkItem item) {
+                    if (item == null) {
+                        return JobService.this.getTransferredUploadBytes();
+                    } else {
+                        return JobService.this.getTransferredUploadBytes(item);
+                    }
+                }
             };
         }
         return mEngine.getBinder();
@@ -171,4 +199,169 @@
      * to end the job entirely.  Regardless of the value returned, your job must stop executing.
      */
     public abstract boolean onStopJob(JobParameters params);
+
+    /**
+     * Update how much data this job will transfer. This method can
+     * be called multiple times within the first 30 seconds after
+     * {@link #onStartJob(JobParameters)} has been called. Only
+     * one call will be heeded after that time has passed.
+     *
+     * This method (or an overload) must be called within the first
+     * 30 seconds for a data transfer job if a payload size estimate
+     * was not provided at the time of scheduling.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     */
+    public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+        mEngine.updateEstimatedNetworkBytes(params, null, downloadBytes, uploadBytes);
+    }
+
+    /**
+     * Update how much data will transfer for the JobWorkItem. This
+     * method can be called multiple times within the first 30 seconds
+     * after {@link #onStartJob(JobParameters)} has been called.
+     * Only one call will be heeded after that time has passed.
+     *
+     * This method (or an overload) must be called within the first
+     * 30 seconds for a data transfer job if a payload size estimate
+     * was not provided at the time of scheduling.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     */
+    public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem jobWorkItem,
+            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+        mEngine.updateEstimatedNetworkBytes(params, jobWorkItem, downloadBytes, uploadBytes);
+    }
+
+    /**
+     * Tell JobScheduler how much data has successfully been transferred for the data transfer job.
+     * @hide
+     */
+    public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+            @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+        mEngine.updateTransferredNetworkBytes(params, null,
+                transferredDownloadBytes, transferredUploadBytes);
+    }
+
+    /**
+     * Tell JobScheduler how much data has been transferred for the data transfer
+     * {@link JobWorkItem}.
+     * @hide
+     */
+    public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item,
+            @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+        mEngine.updateTransferredNetworkBytes(params, item,
+                transferredDownloadBytes, transferredUploadBytes);
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated download bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+     * hasn't been called recently.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredDownloadBytes() {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated upload bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+     * hasn't been called recently.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredUploadBytes() {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated download bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+     * hasn't been called recently and the job has
+     * {@link JobWorkItem JobWorkItems} that have been
+     * {@link JobParameters#dequeueWork dequeued} but not
+     * {@link JobParameters#completeWork(JobWorkItem) completed}.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+        if (item == null) {
+            return getTransferredDownloadBytes();
+        }
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated upload bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+     * hasn't been called recently and the job has
+     * {@link JobWorkItem JobWorkItems} that have been
+     * {@link JobParameters#dequeueWork dequeued} but not
+     * {@link JobParameters#completeWork(JobWorkItem) completed}.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+        if (item == null) {
+            return getTransferredUploadBytes();
+        }
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 3d43d20..83296a6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -16,7 +16,13 @@
 
 package android.app.job;
 
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Service;
+import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
@@ -25,6 +31,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.os.SomeArgs;
+
 import java.lang.ref.WeakReference;
 
 /**
@@ -51,6 +59,20 @@
      * Message that the client has completed execution of this job.
      */
     private static final int MSG_JOB_FINISHED = 2;
+    /**
+     * Message that will result in a call to
+     * {@link #getTransferredDownloadBytes(JobParameters, JobWorkItem)}.
+     */
+    private static final int MSG_GET_TRANSFERRED_DOWNLOAD_BYTES = 3;
+    /**
+     * Message that will result in a call to
+     * {@link #getTransferredUploadBytes(JobParameters, JobWorkItem)}.
+     */
+    private static final int MSG_GET_TRANSFERRED_UPLOAD_BYTES = 4;
+    /** Message that the client wants to update JobScheduler of the data transfer progress. */
+    private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
+    /** Message that the client wants to update JobScheduler of the estimated transfer size. */
+    private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
 
     private final IJobService mBinder;
 
@@ -68,6 +90,32 @@
         }
 
         @Override
+        public void getTransferredDownloadBytes(@NonNull JobParameters jobParams,
+                @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+            JobServiceEngine service = mService.get();
+            if (service != null) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = jobParams;
+                args.arg2 = jobWorkItem;
+                service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_DOWNLOAD_BYTES, args)
+                        .sendToTarget();
+            }
+        }
+
+        @Override
+        public void getTransferredUploadBytes(@NonNull JobParameters jobParams,
+                @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+            JobServiceEngine service = mService.get();
+            if (service != null) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = jobParams;
+                args.arg2 = jobWorkItem;
+                service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_UPLOAD_BYTES, args)
+                        .sendToTarget();
+            }
+        }
+
+        @Override
         public void startJob(JobParameters jobParams) throws RemoteException {
             JobServiceEngine service = mService.get();
             if (service != null) {
@@ -98,9 +146,9 @@
 
         @Override
         public void handleMessage(Message msg) {
-            final JobParameters params = (JobParameters) msg.obj;
             switch (msg.what) {
-                case MSG_EXECUTE_JOB:
+                case MSG_EXECUTE_JOB: {
+                    final JobParameters params = (JobParameters) msg.obj;
                     try {
                         boolean workOngoing = JobServiceEngine.this.onStartJob(params);
                         ackStartMessage(params, workOngoing);
@@ -109,7 +157,9 @@
                         throw new RuntimeException(e);
                     }
                     break;
-                case MSG_STOP_JOB:
+                }
+                case MSG_STOP_JOB: {
+                    final JobParameters params = (JobParameters) msg.obj;
                     try {
                         boolean ret = JobServiceEngine.this.onStopJob(params);
                         ackStopMessage(params, ret);
@@ -118,7 +168,9 @@
                         throw new RuntimeException(e);
                     }
                     break;
-                case MSG_JOB_FINISHED:
+                }
+                case MSG_JOB_FINISHED: {
+                    final JobParameters params = (JobParameters) msg.obj;
                     final boolean needsReschedule = (msg.arg2 == 1);
                     IJobCallback callback = params.getCallback();
                     if (callback != null) {
@@ -132,19 +184,117 @@
                         Log.e(TAG, "finishJob() called for a nonexistent job id.");
                     }
                     break;
+                }
+                case MSG_GET_TRANSFERRED_DOWNLOAD_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final JobWorkItem item = (JobWorkItem) args.arg2;
+                    try {
+                        long ret = JobServiceEngine.this.getTransferredDownloadBytes(params, item);
+                        ackGetTransferredDownloadBytesMessage(params, item, ret);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Application unable to handle getTransferredDownloadBytes.", e);
+                        throw new RuntimeException(e);
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_GET_TRANSFERRED_UPLOAD_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final JobWorkItem item = (JobWorkItem) args.arg2;
+                    try {
+                        long ret = JobServiceEngine.this.getTransferredUploadBytes(params, item);
+                        ackGetTransferredUploadBytesMessage(params, item, ret);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Application unable to handle getTransferredUploadBytes.", e);
+                        throw new RuntimeException(e);
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_UPDATE_TRANSFERRED_NETWORK_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.updateTransferredNetworkBytes(params.getJobId(),
+                                    (JobWorkItem) args.arg2, args.argl1, args.argl2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error updating data transfer progress to system:"
+                                    + " binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG, "updateDataTransferProgress() called for a nonexistent job id.");
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_UPDATE_ESTIMATED_NETWORK_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.updateEstimatedNetworkBytes(params.getJobId(),
+                                    (JobWorkItem) args.arg2, args.argl1, args.argl2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error updating estimated transfer size to system:"
+                                    + " binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG,
+                                "updateEstimatedNetworkBytes() called for a nonexistent job id.");
+                    }
+                    args.recycle();
+                    break;
+                }
                 default:
                     Log.e(TAG, "Unrecognised message received.");
                     break;
             }
         }
 
+        private void ackGetTransferredDownloadBytesMessage(@NonNull JobParameters params,
+                @Nullable JobWorkItem item, long progress) {
+            final IJobCallback callback = params.getCallback();
+            final int jobId = params.getJobId();
+            final int workId = item == null ? -1 : item.getWorkId();
+            if (callback != null) {
+                try {
+                    callback.acknowledgeGetTransferredDownloadBytesMessage(jobId, workId, progress);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "System unreachable for returning progress.");
+                }
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Attempting to ack a job that has already been processed.");
+            }
+        }
+
+        private void ackGetTransferredUploadBytesMessage(@NonNull JobParameters params,
+                @Nullable JobWorkItem item, long progress) {
+            final IJobCallback callback = params.getCallback();
+            final int jobId = params.getJobId();
+            final int workId = item == null ? -1 : item.getWorkId();
+            if (callback != null) {
+                try {
+                    callback.acknowledgeGetTransferredUploadBytesMessage(jobId, workId, progress);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "System unreachable for returning progress.");
+                }
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Attempting to ack a job that has already been processed.");
+            }
+        }
+
         private void ackStartMessage(JobParameters params, boolean workOngoing) {
             final IJobCallback callback = params.getCallback();
             final int jobId = params.getJobId();
             if (callback != null) {
                 try {
                     callback.acknowledgeStartMessage(jobId, workOngoing);
-                } catch(RemoteException e) {
+                } catch (RemoteException e) {
                     Log.e(TAG, "System unreachable for starting job.");
                 }
             } else {
@@ -213,4 +363,73 @@
         m.arg2 = needsReschedule ? 1 : 0;
         m.sendToTarget();
     }
+
+    /**
+     * Engine's request to get how much data has been downloaded.
+     *
+     * @hide
+     * @see JobService#getTransferredDownloadBytes()
+     */
+    @BytesLong
+    public long getTransferredDownloadBytes(@NonNull JobParameters params,
+            @Nullable JobWorkItem item) {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Engine's request to get how much data has been uploaded.
+     *
+     * @hide
+     * @see JobService#getTransferredUploadBytes()
+     */
+    @BytesLong
+    public long getTransferredUploadBytes(@NonNull JobParameters params,
+            @Nullable JobWorkItem item) {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Call in to engine to report data transfer progress.
+     *
+     * @hide
+     * @see JobService#updateTransferredNetworkBytes(JobParameters, long, long)
+     */
+    public void updateTransferredNetworkBytes(@NonNull JobParameters params,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = item;
+        args.argl1 = downloadBytes;
+        args.argl2 = uploadBytes;
+        mHandler.obtainMessage(MSG_UPDATE_TRANSFERRED_NETWORK_BYTES, args).sendToTarget();
+    }
+
+    /**
+     * Call in to engine to report data transfer progress.
+     *
+     * @hide
+     * @see JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)
+     */
+    public void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item,
+            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = item;
+        args.argl1 = downloadBytes;
+        args.argl2 = uploadBytes;
+        mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
+    }
 }
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index e3bd5ac..dcc6aa6 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -314,7 +315,9 @@
     private Sensor mMotionSensor;
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
+    private Bundle mIdleIntentOptions;
     private Intent mLightIdleIntent;
+    private Bundle mLightIdleIntentOptions;
     private AnyMotionDetector mAnyMotionDetector;
     private final AppStateTrackerImpl mAppStateTracker;
     @GuardedBy("this")
@@ -1798,10 +1801,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOnComplete();
                     mGoingIdleWakeLock.release();
@@ -1821,13 +1826,13 @@
                         incActiveIdleOps();
                         mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
                                 mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
-                                null, null, null);
+                                null, null, mIdleIntentOptions);
                     }
                     if (lightChanged) {
                         incActiveIdleOps();
                         mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
                                 mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
-                                null, null, null);
+                                null, null, mLightIdleIntentOptions);
                     }
                     // Always start with one active op for the message being sent here.
                     // Now we are done!
@@ -1849,10 +1854,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
@@ -2531,6 +2538,9 @@
                 mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
                 mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+                mIdleIntentOptions = mLightIdleIntentOptions = options.toBundle();
 
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
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 334647e..9aa6b1c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -21,6 +21,7 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.IJobCallback;
@@ -187,6 +188,18 @@
         public long mStoppedTime;
 
         @Override
+        public void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+                @BytesLong long transferredBytes) {
+            doAcknowledgeGetTransferredDownloadBytesMessage(this, jobId, workId, transferredBytes);
+        }
+
+        @Override
+        public void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId,
+                @BytesLong long transferredBytes) {
+            doAcknowledgeGetTransferredUploadBytesMessage(this, jobId, workId, transferredBytes);
+        }
+
+        @Override
         public void acknowledgeStartMessage(int jobId, boolean ongoing) {
             doAcknowledgeStartMessage(this, jobId, ongoing);
         }
@@ -210,6 +223,18 @@
         public void jobFinished(int jobId, boolean reschedule) {
             doJobFinished(this, jobId, reschedule);
         }
+
+        @Override
+        public void updateEstimatedNetworkBytes(int jobId, JobWorkItem item,
+                long downloadBytes, long uploadBytes) {
+            doUpdateEstimatedNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+        }
+
+        @Override
+        public void updateTransferredNetworkBytes(int jobId, JobWorkItem item,
+                long downloadBytes, long uploadBytes) {
+            doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+        }
     }
 
     JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -506,6 +531,16 @@
         }
     }
 
+    private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback jobCallback, int jobId,
+            int workId, @BytesLong long transferredBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
+    private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback jobCallback, int jobId,
+            int workId, @BytesLong long transferredBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
     void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
         doCallback(cb, reschedule, null);
     }
@@ -558,6 +593,16 @@
         }
     }
 
+    private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
+    private void doUpdateEstimatedNetworkBytes(JobCallback jobCallback, int jobId,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
     /**
      * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
      * we intend to send to the client - we stop sending work when the service is unbound so until
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 c2602f2..145ac52 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -412,6 +412,14 @@
 
     /** Version of the db schema. */
     private static final int JOBS_FILE_VERSION = 1;
+    /**
+     * For legacy reasons, this tag is used to encapsulate the entire job list.
+     */
+    private static final String XML_TAG_JOB_INFO = "job-info";
+    /**
+     * For legacy reasons, this tag represents a single {@link JobStatus} object.
+     */
+    private static final String XML_TAG_JOB = "job";
     /** Tag corresponds to constraints this job needs. */
     private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
     /** Tag corresponds to execution parameters. */
@@ -645,19 +653,19 @@
                 out.startDocument(null, true);
                 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
-                out.startTag(null, "job-info");
+                out.startTag(null, XML_TAG_JOB_INFO);
                 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
                 for (int i=0; i<jobList.size(); i++) {
                     JobStatus jobStatus = jobList.get(i);
                     if (DEBUG) {
                         Slog.d(TAG, "Saving job " + jobStatus.getJobId());
                     }
-                    out.startTag(null, "job");
+                    out.startTag(null, XML_TAG_JOB);
                     addAttributesToJobTag(out, jobStatus);
                     writeConstraintsToXml(out, jobStatus);
                     writeExecutionCriteriaToXml(out, jobStatus);
                     writeBundleToXml(jobStatus.getJob().getExtras(), out);
-                    out.endTag(null, "job");
+                    out.endTag(null, XML_TAG_JOB);
 
                     numJobs++;
                     if (jobStatus.getUid() == Process.SYSTEM_UID) {
@@ -667,7 +675,7 @@
                         }
                     }
                 }
-                out.endTag(null, "job-info");
+                out.endTag(null, XML_TAG_JOB_INFO);
                 out.endDocument();
 
                 file.finishWrite(fos);
@@ -903,17 +911,17 @@
                 return;
             }
             boolean needFileMigration = false;
-            long now = sElapsedRealtimeClock.millis();
+            long nowElapsed = sElapsedRealtimeClock.millis();
             for (File file : files) {
                 final AtomicFile aFile = createJobFile(file);
                 try (FileInputStream fis = aFile.openRead()) {
                     synchronized (mLock) {
-                        jobs = readJobMapImpl(fis, rtcGood);
+                        jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
                         if (jobs != null) {
                             for (int i = 0; i < jobs.size(); i++) {
                                 JobStatus js = jobs.get(i);
                                 js.prepareLocked();
-                                js.enqueueTime = now;
+                                js.enqueueTime = nowElapsed;
                                 this.jobSet.add(js);
 
                                 numJobs++;
@@ -959,7 +967,7 @@
             }
         }
 
-        private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood)
+        private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood, long nowElapsed)
                 throws XmlPullParserException, IOException {
             TypedXmlPullParser parser = Xml.resolvePullParser(fis);
 
@@ -977,28 +985,24 @@
             }
 
             String tagName = parser.getName();
-            if ("job-info".equals(tagName)) {
+            if (XML_TAG_JOB_INFO.equals(tagName)) {
                 final List<JobStatus> jobs = new ArrayList<JobStatus>();
-                final int version;
+                final int version = parser.getAttributeInt(null, "version");
                 // Read in version info.
-                try {
-                    version = Integer.parseInt(parser.getAttributeValue(null, "version"));
-                    if (version > JOBS_FILE_VERSION || version < 0) {
-                        Slog.d(TAG, "Invalid version number, aborting jobs file read.");
-                        return null;
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.e(TAG, "Invalid version number, aborting jobs file read.");
+                if (version > JOBS_FILE_VERSION || version < 0) {
+                    Slog.d(TAG, "Invalid version number, aborting jobs file read.");
                     return null;
                 }
+
                 eventType = parser.next();
                 do {
                     // Read each <job/>
                     if (eventType == XmlPullParser.START_TAG) {
                         tagName = parser.getName();
                         // Start reading job.
-                        if ("job".equals(tagName)) {
-                            JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser, version);
+                        if (XML_TAG_JOB.equals(tagName)) {
+                            JobStatus persistedJob =
+                                    restoreJobFromXml(rtcIsGood, parser, version, nowElapsed);
                             if (persistedJob != null) {
                                 if (DEBUG) {
                                     Slog.d(TAG, "Read out " + persistedJob);
@@ -1022,7 +1026,7 @@
          * @return Newly instantiated job holding all the information we just read out of the xml tag.
          */
         private JobStatus restoreJobFromXml(boolean rtcIsGood, TypedXmlPullParser parser,
-                int schemaVersion) throws XmlPullParserException, IOException {
+                int schemaVersion, long nowElapsed) throws XmlPullParserException, IOException {
             JobInfo.Builder jobBuilder;
             int uid, sourceUserId;
             long lastSuccessfulRunTime;
@@ -1113,18 +1117,9 @@
             }
 
             // Tuple of (earliest runtime, latest runtime) in UTC.
-            final Pair<Long, Long> rtcRuntimes;
-            try {
-                rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
-            } catch (NumberFormatException e) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Error parsing execution time parameters, skipping.");
-                }
-                return null;
-            }
+            final Pair<Long, Long> rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
 
-            final long elapsedNow = sElapsedRealtimeClock.millis();
-            Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
+            Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, nowElapsed);
 
             if (XML_TAG_PERIODIC.equals(parser.getName())) {
                 try {
@@ -1137,8 +1132,8 @@
                     // from now. This is the latest the periodic could be pushed out. This could
                     // happen if the periodic ran early (at flex time before period), and then the
                     // device rebooted.
-                    if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
-                        final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
+                    if (elapsedRuntimes.second > nowElapsed + periodMillis + flexMillis) {
+                        final long clampedLateRuntimeElapsed = nowElapsed + flexMillis
                                 + periodMillis;
                         final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
                                 - flexMillis;
@@ -1163,11 +1158,11 @@
             } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
                 try {
                     if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
-                        jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
+                        jobBuilder.setMinimumLatency(elapsedRuntimes.first - nowElapsed);
                     }
                     if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
                         jobBuilder.setOverrideDeadline(
-                                elapsedRuntimes.second - elapsedNow);
+                                elapsedRuntimes.second - nowElapsed);
                     }
                 } catch (NumberFormatException e) {
                     Slog.d(TAG, "Error reading job execution criteria, skipping.");
@@ -1236,7 +1231,7 @@
 
             // And now we're done
             final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
-                    sourceUserId, elapsedNow);
+                    sourceUserId, nowElapsed);
             JobStatus js = new JobStatus(
                     builtJob, uid, sourcePackageName, sourceUserId,
                     appBucket, sourceTag,
@@ -1246,9 +1241,10 @@
             return js;
         }
 
-        private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
+        private JobInfo.Builder buildBuilderFromXml(TypedXmlPullParser parser)
+                throws XmlPullParserException {
             // Pull out required fields from <job> attributes.
-            int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
+            int jobId = parser.getAttributeInt(null, "jobid");
             String packageName = parser.getAttributeValue(null, "package");
             String className = parser.getAttributeValue(null, "class");
             ComponentName cname = new ComponentName(packageName, className);
@@ -1405,20 +1401,13 @@
          * @return A Pair of timestamps in UTC wall-clock time.  The first is the earliest
          *     time at which the job is to become runnable, and the second is the deadline at
          *     which it becomes overdue to execute.
-         * @throws NumberFormatException
          */
-        private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
-                throws NumberFormatException {
-            String val;
+        private Pair<Long, Long> buildRtcExecutionTimesFromXml(TypedXmlPullParser parser) {
             // Pull out execution time data.
-            val = parser.getAttributeValue(null, "delay");
-            final long earliestRunTimeRtc = (val != null)
-                    ? Long.parseLong(val)
-                    : JobStatus.NO_EARLIEST_RUNTIME;
-            val = parser.getAttributeValue(null, "deadline");
-            final long latestRunTimeRtc = (val != null)
-                    ? Long.parseLong(val)
-                    : JobStatus.NO_LATEST_RUNTIME;
+            final long earliestRunTimeRtc =
+                    parser.getAttributeLong(null, "delay", JobStatus.NO_EARLIEST_RUNTIME);
+            final long latestRunTimeRtc =
+                    parser.getAttributeLong(null, "deadline", JobStatus.NO_LATEST_RUNTIME);
             return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index abc196f..b84c8a4 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -286,7 +286,7 @@
 
         for (int i = 0; i < pkgNames.size(); ++i) {
             final String pkgName = pkgNames.valueAt(i);
-            final boolean isVip = mIrs.isVip(userId, pkgName);
+            final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
             SparseArrayMap<String, OngoingEvent> ongoingEvents =
                     mCurrentOngoingEvents.get(userId, pkgName);
             if (ongoingEvents != null) {
@@ -321,7 +321,7 @@
         final long nowElapsed = SystemClock.elapsedRealtime();
         final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
 
-        final boolean isVip = mIrs.isVip(userId, pkgName);
+        final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
         SparseArrayMap<String, OngoingEvent> ongoingEvents =
                 mCurrentOngoingEvents.get(userId, pkgName);
         if (ongoingEvents != null) {
@@ -397,7 +397,7 @@
                 if (actionAffordabilityNotes != null) {
                     final int size = actionAffordabilityNotes.size();
                     final long newBalance = getBalanceLocked(userId, pkgName);
-                    final boolean isVip = mIrs.isVip(userId, pkgName);
+                    final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
                         note.recalculateCosts(economicPolicy, userId, pkgName);
@@ -503,7 +503,8 @@
                     "Tried to adjust system balance for " + appToString(userId, pkgName));
             return;
         }
-        if (mIrs.isVip(userId, pkgName)) {
+        final boolean isVip = mIrs.isVip(userId, pkgName);
+        if (isVip) {
             // This could happen if the app was made a VIP after it started performing actions.
             // Continue recording the transaction for debugging purposes, but don't let it change
             // any numbers.
@@ -536,7 +537,6 @@
                     mActionAffordabilityNotes.get(userId, pkgName);
             if (actionAffordabilityNotes != null) {
                 final long newBalance = ledger.getCurrentBalance();
-                final boolean isVip = mIrs.isVip(userId, pkgName);
                 for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
                     final boolean isAffordable = isVip
@@ -830,7 +830,6 @@
 
     @GuardedBy("mLock")
     void onUserRemovedLocked(final int userId) {
-        mScribe.discardLedgersLocked(userId);
         mCurrentOngoingEvents.delete(userId);
         mBalanceThresholdAlarmQueue.removeAlarmsForUserId(userId);
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
index fcb3e67..1ff389d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -16,14 +16,20 @@
 
 package com.android.server.tare;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
+import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 
+import com.android.internal.util.ArrayUtils;
+
 /** POJO to cache only the information about installed packages that TARE cares about. */
 class InstalledPackageInfo {
     static final int NO_UID = -1;
@@ -31,14 +37,22 @@
     public final int uid;
     public final String packageName;
     public final boolean hasCode;
+    public final boolean isSystemInstaller;
     @Nullable
     public final String installerPackageName;
 
-    InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+    InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) {
         final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
         uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
         packageName = packageInfo.packageName;
         hasCode = applicationInfo != null && applicationInfo.hasCode();
+        isSystemInstaller = applicationInfo != null
+                && ArrayUtils.indexOf(
+                packageInfo.requestedPermissions, Manifest.permission.INSTALL_PACKAGES) >= 0
+                && PackageManager.PERMISSION_GRANTED
+                == PermissionChecker.checkPermissionForPreflight(context,
+                Manifest.permission.INSTALL_PACKAGES, PermissionChecker.PID_UNKNOWN,
+                applicationInfo.uid, packageName);
         InstallSourceInfo installSourceInfo = null;
         try {
             installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 17b8746..4001d9b 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -64,6 +64,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
 import android.util.SparseSetArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -108,6 +109,16 @@
     /** The amount of time to delay reclamation by after boot. */
     private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
     /**
+     * The amount of time after TARE has first been set up that a system installer will be allowed
+     * expanded credit privileges.
+     */
+    static final long INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS = 7 * DAY_IN_MILLIS;
+    /**
+     * The amount of time to wait after TARE has first been set up before considering adjusting the
+     * stock/consumption limit.
+     */
+    private static final long STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS = 5 * DAY_IN_MILLIS;
+    /**
      * The battery level above which we may consider quantitative easing (increasing the consumption
      * limit).
      */
@@ -127,7 +138,7 @@
     private static final long STOCK_RECALCULATION_MIN_DATA_DURATION_MS = 8 * HOUR_IN_MILLIS;
     private static final int PACKAGE_QUERY_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                    | PackageManager.MATCH_APEX;
+                    | PackageManager.MATCH_APEX | PackageManager.GET_PERMISSIONS;
 
     /** Global lock for all resource economy state. */
     private final Object mLock = new Object();
@@ -179,6 +190,13 @@
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
 
+    /**
+     * Set of temporary Very Important Packages and when their VIP status ends, in the elapsed
+     * realtime ({@link android.annotation.ElapsedRealtimeLong}) timebase.
+     */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, Long> mTemporaryVips = new SparseArrayMap<>();
+
     /** Set of apps each installer is responsible for installing. */
     @GuardedBy("mLock")
     private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
@@ -308,6 +326,7 @@
     private static final int MSG_PROCESS_USAGE_EVENT = 2;
     private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
     private static final int MSG_NOTIFY_STATE_CHANGE_LISTENER = 4;
+    private static final int MSG_CLEAN_UP_TEMP_VIP_LIST = 5;
     private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
 
     /**
@@ -413,6 +432,13 @@
         return userPkgs;
     }
 
+    @Nullable
+    InstalledPackageInfo getInstalledPackageInfo(final int userId, @NonNull final String pkgName) {
+        synchronized (mLock) {
+            return mPkgCache.get(userId, pkgName);
+        }
+    }
+
     @GuardedBy("mLock")
     long getConsumptionLimitLocked() {
         return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
@@ -429,6 +455,11 @@
         return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
     }
 
+
+    long getRealtimeSinceFirstSetupMs() {
+        return mScribe.getRealtimeSinceFirstSetupMs(SystemClock.elapsedRealtime());
+    }
+
     int getUid(final int userId, @NonNull final String pkgName) {
         synchronized (mPackageToUidCache) {
             Integer uid = mPackageToUidCache.get(userId, pkgName);
@@ -470,6 +501,10 @@
     }
 
     boolean isVip(final int userId, @NonNull String pkgName) {
+        return isVip(userId, pkgName, SystemClock.elapsedRealtime());
+    }
+
+    boolean isVip(final int userId, @NonNull String pkgName, final long nowElapsed) {
         synchronized (mLock) {
             final Boolean override = mVipOverrides.get(userId, pkgName);
             if (override != null) {
@@ -481,6 +516,12 @@
             // operate.
             return true;
         }
+        synchronized (mLock) {
+            final Long expirationTimeElapsed = mTemporaryVips.get(userId, pkgName);
+            if (expirationTimeElapsed != null) {
+                return nowElapsed <= expirationTimeElapsed;
+            }
+        }
         return false;
     }
 
@@ -569,7 +610,7 @@
             mPackageToUidCache.add(userId, pkgName, uid);
         }
         synchronized (mLock) {
-            final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo);
+            final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo);
             final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
             maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             mUidToPackageCache.add(uid, pkgName);
@@ -626,11 +667,16 @@
             final List<PackageInfo> pkgs =
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
-                final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+                final InstalledPackageInfo ipo =
+                        new InstalledPackageInfo(getContext(), pkgs.get(i));
                 final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
                 maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
             mAgent.grantBirthrightsLocked(userId);
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            mScribe.setUserAddedTimeLocked(userId, nowElapsed);
+            grantInstallersTemporaryVipStatusLocked(userId,
+                    nowElapsed, INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS);
         }
     }
 
@@ -647,6 +693,7 @@
             mInstallers.delete(userId);
             mPkgCache.delete(userId);
             mAgent.onUserRemovedLocked(userId);
+            mScribe.onUserRemovedLocked(userId);
         }
     }
 
@@ -659,6 +706,10 @@
             maybeAdjustDesiredStockLevelLocked();
             return;
         }
+        if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) {
+            // Things can be very tumultuous soon after first setup.
+            return;
+        }
         // We don't need to increase the limit if the device runs out of consumable credits
         // when the battery is low.
         final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked();
@@ -687,6 +738,10 @@
         if (!mConfigObserver.ENABLE_TIP3) {
             return;
         }
+        if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) {
+            // Things can be very tumultuous soon after first setup.
+            return;
+        }
         // Don't adjust the limit too often or while the battery is low.
         final long now = getCurrentTimeMillis();
         if ((now - mScribe.getLastStockRecalculationTimeLocked()) < STOCK_RECALCULATION_DELAY_MS
@@ -776,6 +831,28 @@
     }
 
     @GuardedBy("mLock")
+    private void grantInstallersTemporaryVipStatusLocked(int userId, long nowElapsed,
+            long grantDurationMs) {
+        final long grantEndTimeElapsed = nowElapsed + grantDurationMs;
+        final int uIdx = mPkgCache.indexOfKey(userId);
+        if (uIdx < 0) {
+            return;
+        }
+        for (int pIdx = mPkgCache.numElementsForKey(uIdx) - 1; pIdx >= 0; --pIdx) {
+            final InstalledPackageInfo ipo = mPkgCache.valueAt(uIdx, pIdx);
+
+            if (ipo.isSystemInstaller) {
+                final Long currentGrantEndTimeElapsed = mTemporaryVips.get(userId, ipo.packageName);
+                if (currentGrantEndTimeElapsed == null
+                        || currentGrantEndTimeElapsed < grantEndTimeElapsed) {
+                    mTemporaryVips.add(userId, ipo.packageName, grantEndTimeElapsed);
+                }
+            }
+        }
+        mHandler.sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST, grantDurationMs);
+    }
+
+    @GuardedBy("mLock")
     private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
         if (!mIsEnabled) {
             return;
@@ -870,7 +947,8 @@
             final List<PackageInfo> pkgs =
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
-                final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+                final InstalledPackageInfo ipo =
+                        new InstalledPackageInfo(getContext(), pkgs.get(i));
                 final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
                 maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
@@ -953,11 +1031,17 @@
         synchronized (mLock) {
             mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties());
             loadInstalledPackageListLocked();
+            final SparseLongArray timeSinceUsersAdded;
             final boolean isFirstSetup = !mScribe.recordExists();
+            final long nowElapsed = SystemClock.elapsedRealtime();
             if (isFirstSetup) {
                 mAgent.grantBirthrightsLocked();
                 mScribe.setConsumptionLimitLocked(
                         mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+                // Set the last reclamation time to now so we don't start reclaiming assets
+                // too early.
+                mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
+                timeSinceUsersAdded = new SparseLongArray();
             } else {
                 mScribe.loadFromDiskLocked();
                 if (mScribe.getSatiatedConsumptionLimitLocked()
@@ -971,6 +1055,21 @@
                     // Adjust the supply in case battery level changed while the device was off.
                     adjustCreditSupplyLocked(true);
                 }
+                timeSinceUsersAdded = mScribe.getRealtimeSinceUsersAddedLocked(nowElapsed);
+            }
+
+            final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
+            for (int userId : userIds) {
+                final long timeSinceUserAddedMs = timeSinceUsersAdded.get(userId, 0);
+                // Temporarily mark installers as VIPs so they aren't subject to credit
+                // limits and policies on first boot.
+                if (timeSinceUserAddedMs < INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS) {
+                    final long remainingGraceDurationMs =
+                            INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS - timeSinceUserAddedMs;
+
+                    grantInstallersTemporaryVipStatusLocked(userId, nowElapsed,
+                            remainingGraceDurationMs);
+                }
             }
             scheduleUnusedWealthReclamationLocked();
         }
@@ -1079,6 +1178,36 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_CLEAN_UP_TEMP_VIP_LIST: {
+                    removeMessages(MSG_CLEAN_UP_TEMP_VIP_LIST);
+
+                    synchronized (mLock) {
+                        final long nowElapsed = SystemClock.elapsedRealtime();
+
+                        long earliestExpiration = Long.MAX_VALUE;
+                        for (int u = 0; u < mTemporaryVips.numMaps(); ++u) {
+                            final int userId = mTemporaryVips.keyAt(u);
+
+                            for (int p = mTemporaryVips.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+                                final String pkgName = mTemporaryVips.keyAt(u, p);
+                                final Long expiration = mTemporaryVips.valueAt(u, p);
+
+                                if (expiration == null || expiration < nowElapsed) {
+                                    mTemporaryVips.delete(userId, pkgName);
+                                } else {
+                                    earliestExpiration = Math.min(earliestExpiration, expiration);
+                                }
+                            }
+                        }
+
+                        if (earliestExpiration < Long.MAX_VALUE) {
+                            sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST,
+                                    earliestExpiration - nowElapsed);
+                        }
+                    }
+                }
+                break;
+
                 case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
                     final SomeArgs args = (SomeArgs) msg.obj;
                     final int userId = args.argi1;
@@ -1558,6 +1687,7 @@
             boolean printedVips = false;
             pw.println();
             pw.print("VIPs:");
+            pw.increaseIndent();
             for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
                 final int userId = mVipOverrides.keyAt(u);
 
@@ -1576,6 +1706,32 @@
             } else {
                 pw.print(" None");
             }
+            pw.decreaseIndent();
+            pw.println();
+
+            boolean printedTempVips = false;
+            pw.println();
+            pw.print("Temp VIPs:");
+            pw.increaseIndent();
+            for (int u = 0; u < mTemporaryVips.numMaps(); ++u) {
+                final int userId = mTemporaryVips.keyAt(u);
+
+                for (int p = 0; p < mTemporaryVips.numElementsForKeyAt(u); ++p) {
+                    final String pkgName = mTemporaryVips.keyAt(u, p);
+
+                    printedTempVips = true;
+                    pw.println();
+                    pw.print(appToString(userId, pkgName));
+                    pw.print("=");
+                    pw.print(mTemporaryVips.valueAt(u, p));
+                }
+            }
+            if (printedTempVips) {
+                pw.println();
+            } else {
+                pw.print(" None");
+            }
+            pw.decreaseIndent();
             pw.println();
 
             pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 7cf459c..c2a6e43 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -117,6 +117,7 @@
 import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE;
+import static com.android.server.tare.TareUtils.appToString;
 import static com.android.server.tare.TareUtils.cakeToString;
 
 import android.annotation.NonNull;
@@ -210,6 +211,22 @@
         if (mIrs.isPackageRestricted(userId, pkgName)) {
             return 0;
         }
+        final InstalledPackageInfo ipo = mIrs.getInstalledPackageInfo(userId, pkgName);
+        if (ipo == null) {
+            Slog.wtfStack(TAG,
+                    "Tried to get max balance of invalid app: " + appToString(userId, pkgName));
+        } else {
+            // A system installer's max balance is elevated for some time after first boot so
+            // they can use jobs to download and install apps.
+            if (ipo.isSystemInstaller) {
+                final long timeSinceFirstSetupMs = mIrs.getRealtimeSinceFirstSetupMs();
+                final boolean stillExempted = timeSinceFirstSetupMs
+                        < InternalResourceService.INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS;
+                if (stillExempted) {
+                    return mMaxSatiatedConsumptionLimit;
+                }
+            }
+        }
         return mMaxSatiatedBalance;
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index ee448b5..b41c0d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Environment;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -33,6 +34,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -88,6 +90,7 @@
             "lastStockRecalculationTime";
     private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
     private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
+    private static final String XML_ATTR_TIME_SINCE_FIRST_SETUP_MS = "timeSinceFirstSetup";
     private static final String XML_ATTR_PR_DISCHARGE = "discharge";
     private static final String XML_ATTR_PR_BATTERY_LEVEL = "batteryLevel";
     private static final String XML_ATTR_PR_PROFIT = "profit";
@@ -112,6 +115,11 @@
     private final InternalResourceService mIrs;
     private final Analyst mAnalyst;
 
+    /**
+     * The value of elapsed realtime since TARE was first setup that was read from disk.
+     * This will only be changed when the persisted file is read.
+     */
+    private long mLoadedTimeSinceFirstSetup;
     @GuardedBy("mIrs.getLock()")
     private long mLastReclamationTime;
     @GuardedBy("mIrs.getLock()")
@@ -122,6 +130,9 @@
     private long mRemainingConsumableCakes;
     @GuardedBy("mIrs.getLock()")
     private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
+    /** Offsets used to calculate the total realtime since each user was added. */
+    @GuardedBy("mIrs.getLock()")
+    private final SparseLongArray mRealtimeSinceUsersAddedOffsets = new SparseLongArray();
 
     private final Runnable mCleanRunnable = this::cleanupLedgers;
     private final Runnable mWriteRunnable = this::writeState;
@@ -163,8 +174,9 @@
     }
 
     @GuardedBy("mIrs.getLock()")
-    void discardLedgersLocked(final int userId) {
+    void onUserRemovedLocked(final int userId) {
         mLedgers.delete(userId);
+        mRealtimeSinceUsersAddedOffsets.delete(userId);
         postWrite();
     }
 
@@ -215,6 +227,11 @@
         return sum;
     }
 
+    /** Returns the cumulative elapsed realtime since TARE was first setup. */
+    long getRealtimeSinceFirstSetupMs(long nowElapsed) {
+        return mLoadedTimeSinceFirstSetup + nowElapsed;
+    }
+
     /** Returns the total amount of cakes that remain to be consumed. */
     @GuardedBy("mIrs.getLock()")
     long getRemainingConsumableCakesLocked() {
@@ -222,6 +239,16 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    SparseLongArray getRealtimeSinceUsersAddedLocked(long nowElapsed) {
+        final SparseLongArray realtimes = new SparseLongArray();
+        for (int i = mRealtimeSinceUsersAddedOffsets.size() - 1; i >= 0; --i) {
+            realtimes.put(mRealtimeSinceUsersAddedOffsets.keyAt(i),
+                    mRealtimeSinceUsersAddedOffsets.valueAt(i) + nowElapsed);
+        }
+        return realtimes;
+    }
+
+    @GuardedBy("mIrs.getLock()")
     void loadFromDiskLocked() {
         mLedgers.clear();
         if (!recordExists()) {
@@ -276,7 +303,8 @@
                 }
             }
 
-            final long endTimeCutoff = System.currentTimeMillis() - MAX_TRANSACTION_AGE_MS;
+            final long now = System.currentTimeMillis();
+            final long endTimeCutoff = now - MAX_TRANSACTION_AGE_MS;
             long earliestEndTime = Long.MAX_VALUE;
             for (eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
                     eventType = parser.next()) {
@@ -294,6 +322,12 @@
                                 parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
                         mLastStockRecalculationTime = parser.getAttributeLong(null,
                                 XML_ATTR_LAST_STOCK_RECALCULATION_TIME, 0);
+                        mLoadedTimeSinceFirstSetup =
+                                parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                                        // If there's no recorded time since first setup, then
+                                        // offset the current elapsed time so it doesn't shift the
+                                        // timing too much.
+                                        -SystemClock.elapsedRealtime());
                         mSatiatedConsumptionLimit =
                                 parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
                                         mIrs.getInitialSatiatedConsumptionLimitLocked());
@@ -356,6 +390,13 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    void setUserAddedTimeLocked(int userId, long timeElapsed) {
+        // Use the current time as an offset so that when we persist the time, it correctly persists
+        // as "time since now".
+        mRealtimeSinceUsersAddedOffsets.put(userId, -timeElapsed);
+    }
+
+    @GuardedBy("mIrs.getLock()")
     void tearDownLocked() {
         TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
         TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
@@ -486,6 +527,14 @@
             // Don't return early since we need to go through all the ledger tags and get to the end
             // of the user tag.
         }
+        if (curUser != UserHandle.USER_NULL) {
+            mRealtimeSinceUsersAddedOffsets.put(curUser,
+                            parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                                    // If there's no recorded time since first setup, then
+                                    // offset the current elapsed time so it doesn't shift the
+                                    // timing too much.
+                                    -SystemClock.elapsedRealtime()));
+        }
         long earliestEndTime = Long.MAX_VALUE;
 
         for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
@@ -630,6 +679,8 @@
                 out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
                 out.attributeLong(null,
                         XML_ATTR_LAST_STOCK_RECALCULATION_TIME, mLastStockRecalculationTime);
+                out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                        mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime());
                 out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
                 out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_CAKES,
                         mRemainingConsumableCakes);
@@ -665,6 +716,9 @@
 
         out.startTag(null, XML_TAG_USER);
         out.attributeInt(null, XML_ATTR_USER_ID, userId);
+        out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                mRealtimeSinceUsersAddedOffsets.get(userId,
+                        mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()));
         for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
             final String pkgName = mLedgers.keyAt(uIdx, pIdx);
             final Ledger ledger = mLedgers.get(userId, pkgName);
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 5e189f2..7b38bd1 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -39,7 +39,7 @@
   void Write8(uint8_t value);
   void Write16(uint16_t value);
   void Write32(uint32_t value);
-  void WriteString(const StringPiece& value);
+  void WriteString(StringPiece value);
   std::ostream& stream_;
 };
 
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 4b271a1..8976924 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -38,7 +38,7 @@
   stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
 }
 
-void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+void BinaryStreamVisitor::WriteString(StringPiece value) {
   // pad with null to nearest word boundary;
   size_t padding_size = CalculatePadding(value.size());
   Write32(value.size());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index d517e29..dd5be21c 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -101,10 +101,10 @@
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
-  using ConfigMap = std::map<std::string, TargetValue>;
-  using EntryMap = std::map<std::string, ConfigMap>;
-  using TypeMap = std::map<std::string, EntryMap>;
-  using PackageMap = std::map<std::string, TypeMap>;
+  using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
+  using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
+  using TypeMap = std::map<std::string, EntryMap, std::less<>>;
+  using PackageMap = std::map<std::string, TypeMap, std::less<>>;
   PackageMap package_map;
   android::StringPool string_pool;
   for (const auto& res_entry : entries_) {
@@ -116,8 +116,7 @@
       return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
     }
 
-    std::string package_name =
-        package_substr.empty() ? target_package_name_ : package_substr.to_string();
+    std::string_view package_name = package_substr.empty() ? target_package_name_ : package_substr;
     if (type_name.empty()) {
       return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
     }
@@ -133,17 +132,14 @@
                     .first;
     }
 
-    auto type = package->second.find(type_name.to_string());
+    auto type = package->second.find(type_name);
     if (type == package->second.end()) {
-      type =
-          package->second
-              .insert(std::make_pair(type_name.to_string(), EntryMap()))
-              .first;
+      type = package->second.insert(std::make_pair(type_name, EntryMap())).first;
     }
 
-    auto entry = type->second.find(entry_name.to_string());
+    auto entry = type->second.find(entry_name);
     if (entry == type->second.end()) {
-      entry = type->second.insert(std::make_pair(entry_name.to_string(), ConfigMap())).first;
+      entry = type->second.insert(std::make_pair(entry_name, ConfigMap())).first;
     }
 
     auto value = entry->second.find(res_entry.configuration);
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 813dff1..7c0b937 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -317,7 +317,7 @@
   }
 
   std::unique_ptr<IdmapData> data(new IdmapData());
-  data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string();
+  data->string_pool_data_ = std::string(resource_mapping.GetStringPoolData());
   uint32_t inline_value_count = 0;
   std::set<std::string> config_set;
   for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) {
diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp
index 4e3f54d2..76c70ca 100644
--- a/cmds/idmap2/libidmap2/PolicyUtils.cpp
+++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp
@@ -53,7 +53,7 @@
 
   for (const auto& policy : kPolicyStringToFlag) {
     if ((bitmask & policy.second) != 0) {
-      policies.emplace_back(policy.first.to_string());
+      policies.emplace_back(policy.first);
     }
   }
 
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index bb31c11..b2300ce 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -89,7 +89,7 @@
     // If the overlay supplies a target overlayable name, the resource must belong to the
     // overlayable defined with the specified name to be overlaid.
     return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
-                 overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
+                 overlay_info.target_name.c_str(), (*overlayable_info)->name.data());
   }
 
   // Enforce policy restrictions if the resource is declared as overlayable.
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index 20aa7d3..a8aa033 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -38,9 +38,10 @@
 constexpr const mode_t kIdmapFilePermission = S_IRUSR | S_IWUSR;  // u=rw-, g=---, o=---
 
 extern "C" bool
-CreateFrroFile(std::string& out_err_result, std::string& packageName, std::string& overlayName,
-               std::string& targetPackageName, std::optional<std::string>& targetOverlayable,
-               std::vector<FabricatedOverlayEntryParameters>& entries_params,
+CreateFrroFile(std::string& out_err_result, const std::string& packageName,
+               const std::string& overlayName, const std::string& targetPackageName,
+               const std::optional<std::string>& targetOverlayable,
+               const std::vector<FabricatedOverlayEntryParameters>& entries_params,
                const std::string& frro_file_path) {
     android::idmap2::FabricatedOverlay::Builder builder(packageName, overlayName,
                                                         targetPackageName);
@@ -90,9 +91,46 @@
     return true;
 }
 
+static PolicyBitmask GetFulfilledPolicy(const bool isSystem, const bool isVendor,
+                                        const bool isProduct, const bool isTargetSignature,
+                                        const bool isOdm, const bool isOem) {
+    auto fulfilled_policy = static_cast<PolicyBitmask>(PolicyFlags::PUBLIC);
+
+    if (isSystem) {
+        fulfilled_policy |= PolicyFlags::SYSTEM_PARTITION;
+    }
+    if (isVendor) {
+        fulfilled_policy |= PolicyFlags::VENDOR_PARTITION;
+    }
+    if (isProduct) {
+        fulfilled_policy |= PolicyFlags::PRODUCT_PARTITION;
+    }
+    if (isOdm) {
+        fulfilled_policy |= PolicyFlags::ODM_PARTITION;
+    }
+    if (isOem) {
+        fulfilled_policy |= PolicyFlags::OEM_PARTITION;
+    }
+    if (isTargetSignature) {
+        fulfilled_policy |= PolicyFlags::SIGNATURE;
+    }
+
+    // Not support actor_signature and config_overlay_signature
+    fulfilled_policy &=
+            ~(PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::CONFIG_SIGNATURE);
+
+    ALOGV(
+            "fulfilled_policy = 0x%08x, isSystem = %d, isVendor = %d, isProduct = %d,"
+            " isTargetSignature = %d, isOdm = %d, isOem = %d,",
+            fulfilled_policy, isSystem, isVendor, isProduct, isTargetSignature, isOdm, isOem);
+    return fulfilled_policy;
+}
+
 extern "C" bool
 CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std::string& overlayPath,
-                const std::string& idmapPath, const std::string& overlayName) {
+                const std::string& idmapPath, const std::string& overlayName,
+                const bool isSystem, const bool isVendor, const bool isProduct,
+                const bool isTargetSignature, const bool isOdm, const bool isOem) {
     // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap
     // guarantees that existing memory maps will continue to be valid and unaffected. The file must
     // be deleted before attempting to create the idmap, so that if idmap  creation fails, the
@@ -114,14 +152,11 @@
     }
 
     // Overlay self target process. Only allow self-targeting types.
-    const auto fulfilled_policies = static_cast<PolicyBitmask>(
-            PolicyFlags::PUBLIC | PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION |
-            PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | PolicyFlags::ODM_PARTITION |
-            PolicyFlags::OEM_PARTITION | PolicyFlags::ACTOR_SIGNATURE |
-            PolicyFlags::CONFIG_SIGNATURE);
+    const auto fulfilled_policies = GetFulfilledPolicy(isSystem, isVendor, isProduct,
+                                                       isTargetSignature, isOdm, isOem);
 
     const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName,
-                                             fulfilled_policies, false /* enforce_overlayable */);
+                                             fulfilled_policies, true /* enforce_overlayable */);
     if (!idmap) {
         out_err = base::StringPrintf("Failed to create idmap because of %s",
                                      idmap.GetErrorMessage().c_str());
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 7d80493..26e20f6 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -29,12 +29,18 @@
 import java.util.function.Consumer;
 import java.util.concurrent.Executor;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class UsbCommand extends Svc.Command {
     public UsbCommand() {
         super("usb");
     }
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     @Override
     public String shortHelp() {
         return "Control Usb state";
@@ -92,8 +98,10 @@
 
             if ("setFunctions".equals(args[1])) {
                 try {
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    System.out.println("setCurrentFunctions opId:" + operationId);
                     usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
-                            args.length >= 3 ? args[2] : ""));
+                            args.length >= 3 ? args[2] : ""), operationId);
                 } catch (RemoteException e) {
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
diff --git a/core/api/current.txt b/core/api/current.txt
index dd8b3d4..3da406f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1041,6 +1041,7 @@
     field public static final int max = 16843062; // 0x1010136
     field public static final int maxAspectRatio = 16844128; // 0x1010560
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
+    field public static final int maxConcurrentSessionsCount;
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
     field public static final int maxHeight = 16843040; // 0x1010120
@@ -11663,6 +11664,7 @@
 
   public class PackageInstaller {
     method public void abandonSession(int);
+    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
     method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
     method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
     method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11707,6 +11709,35 @@
     field public static final int STATUS_SUCCESS = 0; // 0x0
   }
 
+  public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isRequireAppNotForeground();
+    method public boolean isRequireAppNotInteracting();
+    method public boolean isRequireAppNotTopVisible();
+    method public boolean isRequireDeviceIdle();
+    method public boolean isRequireNotInCall();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
+    field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
+  }
+
+  public static final class PackageInstaller.InstallConstraints.Builder {
+    ctor public PackageInstaller.InstallConstraints.Builder();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+  }
+
+  public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isAllConstraintsSatisfied();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
+  }
+
   public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.graphics.Bitmap getIcon();
@@ -11735,6 +11766,7 @@
     method @NonNull public int[] getChildSessionIds();
     method @NonNull public String[] getNames() throws java.io.IOException;
     method public int getParentSessionId();
+    method public boolean isKeepApplicationEnabledSetting();
     method public boolean isMultiPackage();
     method public boolean isStaged();
     method @NonNull public java.io.InputStream openRead(@NonNull String) throws java.io.IOException;
@@ -11786,6 +11818,7 @@
     method public boolean hasParentSessionId();
     method public boolean isActive();
     method public boolean isCommitted();
+    method public boolean isKeepApplicationEnabledSetting();
     method public boolean isMultiPackage();
     method public boolean isSealed();
     method public boolean isStaged();
@@ -11818,6 +11851,7 @@
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setInstallScenario(int);
+    method public void setKeepApplicationEnabledSetting();
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12982,6 +13016,14 @@
 
 package android.credentials {
 
+  public final class ClearCredentialStateRequest implements android.os.Parcelable {
+    ctor public ClearCredentialStateRequest(@NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getData();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ClearCredentialStateRequest> CREATOR;
+  }
+
   public final class CreateCredentialRequest implements android.os.Parcelable {
     ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
@@ -13009,8 +13051,9 @@
   }
 
   public final class CredentialManager {
-    method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
-    method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
+    method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.CredentialManagerException>);
+    method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
+    method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
   }
 
   public class CredentialManagerException extends java.lang.Exception {
@@ -15438,7 +15481,6 @@
   }
 
   public static class PathIterator.Segment {
-    ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
     method public float getConicWeight();
     method @NonNull public float[] getPoints();
     method @NonNull public int getVerb();
@@ -23523,6 +23565,7 @@
     method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
     method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
     method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
+    method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
     method public void stop();
     method public void transferTo(@NonNull android.media.MediaRoute2Info);
     method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
@@ -23896,6 +23939,22 @@
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
   }
 
+  public final class RouteListingPreference implements android.os.Parcelable {
+    ctor public RouteListingPreference(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference> CREATOR;
+  }
+
+  public static final class RouteListingPreference.Item implements android.os.Parcelable {
+    ctor public RouteListingPreference.Item(@NonNull String);
+    method public int describeContents();
+    method @NonNull public String getRouteId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
+  }
+
   public final class RoutingSessionInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public String getClientPackageName();
@@ -35851,6 +35910,7 @@
     method public static boolean canDrawOverlays(android.content.Context);
     field public static final String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
     field public static final String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
+    field public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS = "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS";
     field public static final String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
     field public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = "android.settings.ALL_APPS_NOTIFICATION_SETTINGS";
     field public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
@@ -35899,7 +35959,6 @@
     field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
     field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
     field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
-    field public static final String ACTION_MEMTAG_SETTINGS = "android.settings.MEMTAG_SETTINGS";
     field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS";
     field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS";
     field public static final String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS";
@@ -39302,6 +39361,149 @@
 
 }
 
+package android.service.credentials {
+
+  public final class Action implements android.os.Parcelable {
+    ctor public Action(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+    method public int describeContents();
+    method @NonNull public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.Action> CREATOR;
+  }
+
+  public final class BeginCreateCredentialRequest implements android.os.Parcelable {
+    ctor public BeginCreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public String getCallingPackage();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialRequest> CREATOR;
+  }
+
+  public final class BeginCreateCredentialResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.service.credentials.CreateEntry> getCreateEntries();
+    method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialResponse> CREATOR;
+  }
+
+  public static final class BeginCreateCredentialResponse.Builder {
+    ctor public BeginCreateCredentialResponse.Builder();
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.CreateEntry);
+  }
+
+  public final class CreateCredentialRequest implements android.os.Parcelable {
+    ctor public CreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public String getCallingPackage();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateCredentialRequest> CREATOR;
+  }
+
+  public final class CreateEntry implements android.os.Parcelable {
+    ctor public CreateEntry(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+    method public int describeContents();
+    method @NonNull public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateEntry> CREATOR;
+  }
+
+  public final class CredentialEntry implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.credentials.Credential getCredential();
+    method @Nullable public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method @NonNull public String getType();
+    method public boolean isAutoSelectAllowed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialEntry> CREATOR;
+  }
+
+  public static final class CredentialEntry.Builder {
+    ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+    ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.credentials.Credential);
+    method @NonNull public android.service.credentials.CredentialEntry build();
+    method @NonNull public android.service.credentials.CredentialEntry.Builder setAutoSelectAllowed(@NonNull boolean);
+  }
+
+  public class CredentialProviderException extends java.lang.Exception {
+    ctor public CredentialProviderException(int, @NonNull String, @NonNull Throwable);
+    ctor public CredentialProviderException(int, @NonNull String);
+    ctor public CredentialProviderException(int, @NonNull Throwable);
+    ctor public CredentialProviderException(int);
+    method public int getErrorCode();
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+  }
+
+  public abstract class CredentialProviderService extends android.app.Service {
+    ctor public CredentialProviderService();
+    method public abstract void onBeginCreateCredential(@NonNull android.service.credentials.BeginCreateCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,android.service.credentials.CredentialProviderException>);
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onGetCredentials(@NonNull android.service.credentials.GetCredentialsRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.GetCredentialsResponse,android.service.credentials.CredentialProviderException>);
+    field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+    field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
+    field public static final String EXTRA_CREATE_CREDENTIAL_RESULT = "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
+    field public static final String EXTRA_CREDENTIAL_RESULT = "android.service.credentials.extra.CREDENTIAL_RESULT";
+    field public static final String EXTRA_ERROR = "android.service.credentials.extra.ERROR";
+    field public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT = "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+    field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService";
+  }
+
+  public final class CredentialsResponseContent implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.service.credentials.Action> getActions();
+    method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
+    method @Nullable public android.service.credentials.CredentialEntry getRemoteCredentialEntry();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialsResponseContent> CREATOR;
+  }
+
+  public static final class CredentialsResponseContent.Builder {
+    ctor public CredentialsResponseContent.Builder();
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addAction(@NonNull android.service.credentials.Action);
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addCredentialEntry(@NonNull android.service.credentials.CredentialEntry);
+    method @NonNull public android.service.credentials.CredentialsResponseContent build();
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.CredentialEntry);
+  }
+
+  public final class GetCredentialsRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getCallingPackage();
+    method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsRequest> CREATOR;
+  }
+
+  public static final class GetCredentialsRequest.Builder {
+    ctor public GetCredentialsRequest.Builder(@NonNull String);
+    method @NonNull public android.service.credentials.GetCredentialsRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+    method @NonNull public android.service.credentials.GetCredentialsRequest build();
+    method @NonNull public android.service.credentials.GetCredentialsRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+  }
+
+  public final class GetCredentialsResponse implements android.os.Parcelable {
+    method @NonNull public static android.service.credentials.GetCredentialsResponse createWithAuthentication(@NonNull android.service.credentials.Action);
+    method @NonNull public static android.service.credentials.GetCredentialsResponse createWithResponseContent(@NonNull android.service.credentials.CredentialsResponseContent);
+    method public int describeContents();
+    method @Nullable public android.service.credentials.Action getAuthenticationAction();
+    method @Nullable public android.service.credentials.CredentialsResponseContent getCredentialsResponseContent();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsResponse> CREATOR;
+  }
+
+}
+
 package android.service.dreams {
 
   public class DreamService extends android.app.Service implements android.view.Window.Callback {
@@ -41838,6 +42040,7 @@
     field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
     field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+    field public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL = "include_lte_for_nr_advanced_threshold_bandwidth_bool";
     field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
     field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool";
     field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b6e2d2a..3228ce6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -121,6 +121,7 @@
     field public static final int GADGET_HAL_V1_0 = 10; // 0xa
     field public static final int GADGET_HAL_V1_1 = 11; // 0xb
     field public static final int GADGET_HAL_V1_2 = 12; // 0xc
+    field public static final int GADGET_HAL_V2_0 = 20; // 0x14
     field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
     field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
     field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 72bff0f..c47e3b2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2958,11 +2958,13 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
     method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
+    method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2980,6 +2982,7 @@
     method public int getLockState();
     method @Nullable public String getName();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
+    method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
@@ -2996,6 +2999,7 @@
   public static final class VirtualDeviceParams.Builder {
     ctor public VirtualDeviceParams.Builder();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3053,6 +3057,50 @@
 
 }
 
+package android.companion.virtual.sensor {
+
+  public class VirtualSensor {
+    method @NonNull public String getName();
+    method public int getType();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+  }
+
+  public static interface VirtualSensor.SensorStateChangeCallback {
+    method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+  }
+
+  public final class VirtualSensorConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getName();
+    method public int getType();
+    method @Nullable public String getVendor();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
+  }
+
+  public static final class VirtualSensorConfig.Builder {
+    ctor public VirtualSensorConfig.Builder(int, @NonNull String);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
+  }
+
+  public final class VirtualSensorEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getTimestampNanos();
+    method @NonNull public float[] getValues();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
+  }
+
+  public static final class VirtualSensorEvent.Builder {
+    ctor public VirtualSensorEvent.Builder(@NonNull float[]);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
+  }
+
+}
+
 package android.content {
 
   public class ApexEnvironment {
@@ -3146,6 +3194,7 @@
     field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
     field public static final String UWB_SERVICE = "uwb";
+    field public static final String VIRTUALIZATION_SERVICE = "virtualization";
     field public static final String VR_SERVICE = "vrmanager";
     field public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE = "wallpaper_effects_generation";
     field public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
@@ -4171,6 +4220,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerStateChangeOnActiveSourceLost();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getRoutingControl();
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSadPresenceInQuery(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSoundbarMode();
     method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioControl();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioModeMuting();
@@ -4192,6 +4242,7 @@
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setRoutingControl(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadPresenceInQuery(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadsPresenceInQuery(@NonNull java.util.List<java.lang.String>, int);
+    method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSoundbarMode(int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioControl(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioModeMuting(@NonNull int);
@@ -4219,6 +4270,7 @@
     field public static final String CEC_SETTING_NAME_QUERY_SAD_TRUEHD = "query_sad_truehd";
     field public static final String CEC_SETTING_NAME_QUERY_SAD_WMAPRO = "query_sad_wmapro";
     field public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
+    field public static final String CEC_SETTING_NAME_SOUNDBAR_MODE = "soundbar_mode";
     field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL = "system_audio_control";
     field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING = "system_audio_mode_muting";
     field public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep";
@@ -4300,6 +4352,8 @@
     field public static final int ROUTING_CONTROL_DISABLED = 0; // 0x0
     field public static final int ROUTING_CONTROL_ENABLED = 1; // 0x1
     field public static final String SETTING_NAME_EARC_ENABLED = "earc_enabled";
+    field public static final int SOUNDBAR_MODE_DISABLED = 0; // 0x0
+    field public static final int SOUNDBAR_MODE_ENABLED = 1; // 0x1
     field public static final int SYSTEM_AUDIO_CONTROL_DISABLED = 0; // 0x0
     field public static final int SYSTEM_AUDIO_CONTROL_ENABLED = 1; // 0x1
     field public static final int SYSTEM_AUDIO_MODE_MUTING_DISABLED = 0; // 0x0
@@ -5527,6 +5581,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_ACCESSORY_HANDSHAKE = "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED = "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED";
     field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
     field public static final String EXTRA_ACCESSORY_HANDSHAKE_END = "android.hardware.usb.extra.ACCESSORY_HANDSHAKE_END";
     field public static final String EXTRA_ACCESSORY_START = "android.hardware.usb.extra.ACCESSORY_START";
@@ -5554,6 +5609,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus();
     method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbPort(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int);
+    method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public boolean supportsComplianceWarnings();
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4
@@ -5579,6 +5635,7 @@
 
   public final class UsbPortStatus implements android.os.Parcelable {
     method public int describeContents();
+    method @CheckResult @NonNull public int[] getComplianceWarnings();
     method public int getCurrentDataRole();
     method public int getCurrentMode();
     method public int getCurrentPowerRole();
@@ -5589,6 +5646,10 @@
     method public boolean isPowerTransferLimited();
     method public boolean isRoleCombinationSupported(int, int);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int COMPLIANCE_WARNING_BC_1_2 = 3; // 0x3
+    field public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2; // 0x2
+    field public static final int COMPLIANCE_WARNING_MISSING_RP = 4; // 0x4
+    field public static final int COMPLIANCE_WARNING_OTHER = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR;
     field public static final int DATA_ROLE_DEVICE = 2; // 0x2
     field public static final int DATA_ROLE_HOST = 1; // 0x1
@@ -6467,7 +6528,6 @@
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
-    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
     field public static final int PLAYER_STATE_IDLE = 1; // 0x1
     field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
@@ -7262,6 +7322,7 @@
     method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]);
     method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
+    method public boolean isLnaSupported();
     method public boolean isLowestPriority(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
@@ -10035,6 +10096,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.NewUserResponse createUser(@NonNull android.os.NewUserRequest);
     method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
     method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -10562,6 +10624,7 @@
     field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
     field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
     field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
+    field public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
     field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
     field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
     field public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
@@ -16136,6 +16199,12 @@
   public abstract class AccessibilityDisplayProxy {
     ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
     method public int getDisplayId();
+    method @NonNull public final java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAndEnabledServices();
+    method @NonNull public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
+    method public void interrupt();
+    method public void onAccessibilityEvent(@NonNull android.view.accessibility.AccessibilityEvent);
+    method public void onProxyConnected();
+    method public void setInstalledAndEnabledServices(@NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
   }
 
   public final class AccessibilityManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 121741e0..1370575 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -459,6 +459,8 @@
 
   public class WallpaperManager {
     method @Nullable public android.graphics.Bitmap getBitmap();
+    method @Nullable public android.graphics.Rect peekBitmapDimensions();
+    method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
     method public boolean shouldEnableWideColorGamut();
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
   }
@@ -1037,6 +1039,7 @@
     method @NonNull public static android.util.Pair<java.util.List<android.graphics.Typeface>,java.util.List<android.graphics.Typeface>> changeDefaultFontForTest(@NonNull java.util.List<android.graphics.Typeface>, @NonNull java.util.List<android.graphics.Typeface>);
     method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws java.io.IOException;
     method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory();
+    method public void releaseNativeObjectForTest();
     method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws android.system.ErrnoException, java.io.IOException;
   }
 
@@ -1249,6 +1252,7 @@
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
     field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
     field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
+    field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
     field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
   }
 
@@ -1905,6 +1909,7 @@
   }
 
   public abstract class VibrationEffect implements android.os.Parcelable {
+    method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
     method public static android.os.VibrationEffect get(int);
     method public static android.os.VibrationEffect get(int, boolean);
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
@@ -1919,6 +1924,7 @@
   }
 
   public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+    method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
     method public long getDuration();
     method public int getRepeatIndex();
     method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 4d4a4d7..e447d86 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
                 mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
                         mCallingUid);
                 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
-                startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+                startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
                 return;
             }
         } catch (OperationCanceledException e) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a61ade0..884870b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -47,6 +47,8 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -799,7 +801,7 @@
         ApplicationInfo appInfo;
         int backupMode;
         int userId;
-        int operationType;
+        @BackupDestination int backupDestination;
         public String toString() {
             return "CreateBackupAgentData{appInfo=" + appInfo
                     + " backupAgent=" + appInfo.backupAgentName
@@ -1034,12 +1036,12 @@
         }
 
         public final void scheduleCreateBackupAgent(ApplicationInfo app,
-                int backupMode, int userId, int operationType) {
+                int backupMode, int userId, @BackupDestination int backupDestination) {
             CreateBackupAgentData d = new CreateBackupAgentData();
             d.appInfo = app;
             d.backupMode = backupMode;
             d.userId = userId;
-            d.operationType = operationType;
+            d.backupDestination = backupDestination;
 
             sendMessage(H.CREATE_BACKUP_AGENT, d);
         }
@@ -4402,7 +4404,8 @@
                     context.setOuterContext(agent);
                     agent.attach(context);
 
-                    agent.onCreate(UserHandle.of(data.userId), data.operationType);
+                    agent.onCreate(UserHandle.of(data.userId), data.backupDestination,
+                            getOperationTypeFromBackupMode(data.backupMode));
                     binder = agent.onBind();
                     backupAgents.put(packageName, agent);
                 } catch (Exception e) {
@@ -4430,6 +4433,22 @@
         }
     }
 
+    @OperationType
+    private static int getOperationTypeFromBackupMode(int backupMode) {
+        switch (backupMode) {
+            case ApplicationThreadConstants.BACKUP_MODE_RESTORE:
+            case ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL:
+                return OperationType.RESTORE;
+            case ApplicationThreadConstants.BACKUP_MODE_FULL:
+            case ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL:
+                return OperationType.BACKUP;
+            default:
+                Slog.w(TAG, "Invalid backup mode when initialising BackupAgent: "
+                        + backupMode);
+                return OperationType.UNKNOWN;
+        }
+    }
+
     private String getBackupAgentName(CreateBackupAgentData data) {
         String agentName = data.appInfo.backupAgentName;
         // full backup operation but no app-supplied agent?  use the default implementation
@@ -6407,23 +6426,28 @@
     }
 
     private void handleTrimMemory(int level) {
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory: " + level);
+        }
         if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
 
-        if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
-            PropertyInvalidatedCache.onTrimMemory();
-        }
+        try {
+            if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+                PropertyInvalidatedCache.onTrimMemory();
+            }
 
-        final ArrayList<ComponentCallbacks2> callbacks =
-                collectComponentCallbacks(true /* includeUiContexts */);
+            final ArrayList<ComponentCallbacks2> callbacks =
+                    collectComponentCallbacks(true /* includeUiContexts */);
 
-        final int N = callbacks.size();
-        for (int i = 0; i < N; i++) {
-            callbacks.get(i).onTrimMemory(level);
+            final int N = callbacks.size();
+            for (int i = 0; i < N; i++) {
+                callbacks.get(i).onTrimMemory(level);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
 
         WindowManagerGlobal.getInstance().trimMemory(level);
-        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
         if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
             unscheduleGcIdler();
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f25e639..9d5c01a 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -767,11 +767,11 @@
      */
     @SystemApi
     public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
-        Preconditions.checkArgument(!namespace.contains("/"),
-                "namespace should not contain '/'");
-        Preconditions.checkArgument(!key.contains("/"),
-                "key should not contain '/'");
-        mDeliveryGroupMatchingKey = namespace + "/" + key;
+        Preconditions.checkArgument(!namespace.contains(":"),
+                "namespace should not contain ':'");
+        Preconditions.checkArgument(!key.contains(":"),
+                "key should not contain ':'");
+        mDeliveryGroupMatchingKey = namespace + ":" + key;
     }
 
     /**
@@ -779,7 +779,7 @@
      * broadcast belongs to.
      *
      * @return the delivery group namespace and key that was previously set using
-     *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}.
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 818bdc2..63fdc2e 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -48,12 +48,14 @@
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.pm.ServiceInfo.ForegroundServiceType;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
+import android.healthconnect.HealthConnectManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.ArraySet;
@@ -66,8 +68,10 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * This class enforces the policies around the foreground service types.
@@ -655,11 +659,12 @@
          *
          * For test only.
          */
-        public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest() {
+        public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest(
+                @NonNull Context context) {
             if (mAllOfPermissions == null) {
                 return Optional.empty();
             }
-            return Optional.of(mAllOfPermissions.toStringArray());
+            return Optional.of(mAllOfPermissions.toStringArray(context));
         }
 
         /**
@@ -668,11 +673,12 @@
          *
          * For test only.
          */
-        public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest() {
+        public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest(
+                @NonNull Context context) {
             if (mAnyOfPermissions == null) {
                 return Optional.empty();
             }
-            return Optional.of(mAnyOfPermissions.toStringArray());
+            return Optional.of(mAnyOfPermissions.toStringArray(context));
         }
 
         /**
@@ -808,12 +814,12 @@
             return sb.toString();
         }
 
-        @NonNull String[] toStringArray() {
-            final String[] names = new String[mPermissions.length];
+        @NonNull String[] toStringArray(Context context) {
+            final ArrayList<String> list = new ArrayList<>();
             for (int i = 0; i < mPermissions.length; i++) {
-                names[i] = mPermissions[i].mName;
+                mPermissions[i].addToList(context, list);
             }
-            return names;
+            return list.toArray(new String[list.size()]);
         }
     }
 
@@ -826,7 +832,7 @@
         /**
          * The name of this permission.
          */
-        final @NonNull String mName;
+        protected final @NonNull String mName;
 
         /**
          * Constructor.
@@ -846,6 +852,10 @@
         public String toString() {
             return mName;
         }
+
+        void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+            list.add(mName);
+        }
     }
 
     /**
@@ -859,15 +869,24 @@
         @Override
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @PackageManager.PermissionResult
-        public int checkPermission(Context context, int callerUid, int callerPid,
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
                 String packageName, boolean allowWhileInUse) {
+            return checkPermission(context, mName, callerUid, callerPid, packageName,
+                    allowWhileInUse);
+        }
+
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
+                int callerPid, String packageName, boolean allowWhileInUse) {
             // Simple case, check if it's already granted.
-            if (context.checkPermission(mName, callerPid, callerUid) == PERMISSION_GRANTED) {
+            if (PermissionChecker.checkPermissionForPreflight(context, name,
+                    callerPid, callerUid, packageName) == PERMISSION_GRANTED) {
                 return PERMISSION_GRANTED;
             }
             if (allowWhileInUse) {
                 // Check its appops
-                final int opCode = AppOpsManager.permissionToOpCode(mName);
+                final int opCode = AppOpsManager.permissionToOpCode(name);
                 final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
                 if (opCode != AppOpsManager.OP_NONE) {
                     final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
@@ -895,7 +914,7 @@
 
         @Override
         @PackageManager.PermissionResult
-        public int checkPermission(Context context, int callerUid, int callerPid,
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
                 String packageName, boolean allowWhileInUse) {
             final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
             final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
@@ -915,7 +934,7 @@
         @Override
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @PackageManager.PermissionResult
-        public int checkPermission(Context context, int callerUid, int callerPid,
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
                 String packageName, boolean allowWhileInUse) {
             final UsbManager usbManager = context.getSystemService(UsbManager.class);
             final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
@@ -941,7 +960,7 @@
         @Override
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @PackageManager.PermissionResult
-        public int checkPermission(Context context, int callerUid, int callerPid,
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
                 String packageName, boolean allowWhileInUse) {
             final UsbManager usbManager = context.getSystemService(UsbManager.class);
             final UsbAccessory[] accessories = usbManager.getAccessoryList();
@@ -956,6 +975,45 @@
         }
     }
 
+    static class HealthConnectPermission extends RegularPermission {
+        private @Nullable String[] mPermissionNames;
+
+        HealthConnectPermission() {
+            super("Health Connect");
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final String[] perms = getPermissions(context);
+            for (String perm : perms) {
+                if (checkPermission(context, perm, callerUid, callerPid,
+                        packageName, allowWhileInUse) == PERMISSION_GRANTED) {
+                    return PERMISSION_GRANTED;
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+
+        @Override
+        void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+            final String[] perms = getPermissions(context);
+            for (String perm : perms) {
+                list.add(perm);
+            }
+        }
+
+        private @NonNull String[] getPermissions(@NonNull Context context) {
+            if (mPermissionNames != null) {
+                return mPermissionNames;
+            }
+            final Set<String> healthPerms = HealthConnectManager.getHealthPermissions(context);
+            return mPermissionNames = healthPerms.toArray(new String[healthPerms.size()]);
+        }
+    }
+
     /**
      * The default policy for the foreground service types.
      *
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 7475ef8..902f172 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -304,7 +304,7 @@
     @UnsupportedAppUsage
     void resumeAppSwitches();
     boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
-            int operationType);
+            int backupDestination);
     void backupAgentCreated(in String packageName, in IBinder agent, int userId);
     void unbindBackupAgent(in ApplicationInfo appInfo);
     int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6857984..5d87012 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -58,6 +58,7 @@
 import android.companion.ICompanionDeviceManager;
 import android.companion.virtual.IVirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceManager;
+import android.compat.Compatibility;
 import android.content.ClipboardManager;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -210,6 +211,7 @@
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.service.vr.IVrManager;
+import android.system.virtualmachine.VirtualizationFrameworkInitializer;
 import android.telecom.TelecomManager;
 import android.telephony.MmsManager;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -1091,7 +1093,10 @@
                 new CachedServiceFetcher<OverlayManager>() {
             @Override
             public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
+                final IBinder b =
+                        (Compatibility.isChangeEnabled(OverlayManager.SELF_TARGETING_OVERLAY))
+                                ? ServiceManager.getService(Context.OVERLAY_SERVICE)
+                                : ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
                 return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b));
             }});
 
@@ -1566,6 +1571,7 @@
             NearbyFrameworkInitializer.registerServiceWrappers();
             OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
             DeviceLockFrameworkInitializer.registerServiceWrappers();
+            VirtualizationFrameworkInitializer.registerServiceWrappers();
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index ef10c0b..f133c8a 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -201,6 +201,23 @@
             "file_patterns": [
                 "(/|^)PropertyInvalidatedCache.java"
             ]
+        },
+        {
+            "name": "FrameworksCoreGameManagerTests",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "org.junit.Ignore"
+                },
+                {
+                    "include-filter": "android.app"
+                }
+            ],
+            "file_patterns": [
+                "(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*"
+            ]
         }
     ],
     "presubmit-large": [
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index b9a7186..f5d657c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -358,13 +358,37 @@
         }
     }
 
+    /**
+     * Convenience class representing a cached wallpaper bitmap and associated data.
+     */
+    private static class CachedWallpaper {
+        final Bitmap mCachedWallpaper;
+        final int mCachedWallpaperUserId;
+        @SetWallpaperFlags final int mWhich;
+
+        CachedWallpaper(Bitmap cachedWallpaper, int cachedWallpaperUserId,
+                @SetWallpaperFlags int which) {
+            mCachedWallpaper = cachedWallpaper;
+            mCachedWallpaperUserId = cachedWallpaperUserId;
+            mWhich = which;
+        }
+
+        /**
+         * Returns true if this object represents a valid cached bitmap for the given parameters,
+         * otherwise false.
+         */
+        boolean isValid(int userId, @SetWallpaperFlags int which) {
+            return userId == mCachedWallpaperUserId && which == mWhich
+                    && !mCachedWallpaper.isRecycled();
+        }
+    }
+
     private static class Globals extends IWallpaperManagerCallback.Stub {
         private final IWallpaperManager mService;
         private boolean mColorCallbackRegistered;
         private final ArrayList<Pair<OnColorsChangedListener, Handler>> mColorListeners =
                 new ArrayList<>();
-        private Bitmap mCachedWallpaper;
-        private int mCachedWallpaperUserId;
+        private CachedWallpaper mCachedWallpaper;
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
         private ArrayMap<LocalWallpaperColorConsumer, ArraySet<RectF>> mLocalColorCallbackAreas =
@@ -536,6 +560,15 @@
                     false /* hardware */, cmProxy);
         }
 
+        /**
+         * Retrieves the current wallpaper Bitmap, caching the result. If this fails and
+         * `returnDefault` is set, returns the Bitmap for the default wallpaper; otherwise returns
+         * null.
+         *
+         * More sophisticated caching might a) store and compare the wallpaper ID so that
+         * consecutive calls for FLAG_SYSTEM and FLAG_LOCK could return the cached wallpaper if
+         * no lock screen wallpaper is set, or b) separately cache home and lock screen wallpaper.
+         */
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
                 @SetWallpaperFlags int which, int userId, boolean hardware,
                 ColorManagementProxy cmProxy) {
@@ -549,16 +582,14 @@
                 }
             }
             synchronized (this) {
-                if (mCachedWallpaper != null && mCachedWallpaperUserId == userId
-                        && !mCachedWallpaper.isRecycled()) {
-                    return mCachedWallpaper;
+                if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) {
+                    return mCachedWallpaper.mCachedWallpaper;
                 }
                 mCachedWallpaper = null;
-                mCachedWallpaperUserId = 0;
+                Bitmap currentWallpaper = null;
                 try {
-                    mCachedWallpaper = getCurrentWallpaperLocked(
-                            context, userId, hardware, cmProxy);
-                    mCachedWallpaperUserId = userId;
+                    currentWallpaper = getCurrentWallpaperLocked(
+                            context, which, userId, hardware, cmProxy);
                 } catch (OutOfMemoryError e) {
                     Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
                 } catch (SecurityException e) {
@@ -570,8 +601,9 @@
                         throw e;
                     }
                 }
-                if (mCachedWallpaper != null) {
-                    return mCachedWallpaper;
+                if (currentWallpaper != null) {
+                    mCachedWallpaper = new CachedWallpaper(currentWallpaper, userId, which);
+                    return currentWallpaper;
                 }
             }
             if (returnDefault) {
@@ -587,7 +619,9 @@
             return null;
         }
 
-        public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+        @Nullable
+        public Rect peekWallpaperDimensions(Context context, boolean returnDefault,
+                @SetWallpaperFlags int which, int userId) {
             if (mService != null) {
                 try {
                     if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -600,11 +634,10 @@
 
             Rect dimensions = null;
             synchronized (this) {
-                ParcelFileDescriptor pfd = null;
-                try {
-                    Bundle params = new Bundle();
-                    pfd = mService.getWallpaperWithFeature(context.getOpPackageName(),
-                            context.getAttributionTag(), this, FLAG_SYSTEM, params, userId);
+                Bundle params = new Bundle();
+                try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+                        context.getOpPackageName(), context.getAttributionTag(), this, which,
+                        params, userId)) {
                     // Let's peek user wallpaper first.
                     if (pfd != null) {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -614,19 +647,14 @@
                     }
                 } catch (RemoteException ex) {
                     Log.w(TAG, "peek wallpaper dimensions failed", ex);
-                } finally {
-                    if (pfd != null) {
-                        try {
-                            pfd.close();
-                        } catch (IOException ignored) {
-                        }
-                    }
+                } catch (IOException ignored) {
+                    // This is only thrown on close and can be safely ignored.
                 }
             }
             // If user wallpaper is unavailable, may be the default one instead.
             if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
                     && returnDefault) {
-                InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+                InputStream is = openDefaultWallpaper(context, which);
                 if (is != null) {
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -644,13 +672,12 @@
         void forgetLoadedWallpaper() {
             synchronized (this) {
                 mCachedWallpaper = null;
-                mCachedWallpaperUserId = 0;
                 mDefaultWallpaper = null;
             }
         }
 
-        private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware,
-                ColorManagementProxy cmProxy) {
+        private Bitmap getCurrentWallpaperLocked(Context context, @SetWallpaperFlags int which,
+                int userId, boolean hardware, ColorManagementProxy cmProxy) {
             if (mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 return null;
@@ -659,7 +686,7 @@
             try {
                 Bundle params = new Bundle();
                 ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
-                        context.getOpPackageName(), context.getAttributionTag(), this, FLAG_SYSTEM,
+                        context.getOpPackageName(), context.getAttributionTag(), this, which,
                         params, userId);
 
                 if (pfd != null) {
@@ -1148,9 +1175,26 @@
      * @return the dimensions of system wallpaper
      * @hide
      */
+    @TestApi
+    @Nullable
     public Rect peekBitmapDimensions() {
-        return sGlobals.peekWallpaperDimensions(
-                mContext, true /* returnDefault */, mContext.getUserId());
+        return peekBitmapDimensions(FLAG_SYSTEM);
+    }
+
+    /**
+     * Peek the dimensions of given wallpaper of the user without decoding it.
+     *
+     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+     *     {@link #FLAG_LOCK}.
+     * @return the dimensions of system wallpaper
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+        checkExactlyOneWallpaperFlagSet(which);
+        return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which,
+                mContext.getUserId());
     }
 
     /**
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index a4f612d..e323e89 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -20,7 +20,8 @@
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.QueuedWork;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -137,7 +138,7 @@
 public abstract class BackupAgent extends ContextWrapper {
     private static final String TAG = "BackupAgent";
     private static final boolean DEBUG = false;
-    private static final int DEFAULT_OPERATION_TYPE = OperationType.BACKUP;
+    private static final int DEFAULT_BACKUP_DESTINATION = BackupDestination.CLOUD;
 
     /** @hide */
     public static final int RESULT_SUCCESS = 0;
@@ -207,7 +208,7 @@
     @Nullable private UserHandle mUser;
      // This field is written from the main thread (in onCreate), and read in a Binder thread (in
      // onFullBackup that is called from system_server via Binder).
-    @OperationType private volatile int mOperationType = DEFAULT_OPERATION_TYPE;
+    @BackupDestination private volatile int mBackupDestination = DEFAULT_BACKUP_DESTINATION;
 
     Handler getHandler() {
         if (mHandler == null) {
@@ -265,13 +266,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void onCreate(UserHandle user) {
-        onCreate(user, DEFAULT_OPERATION_TYPE);
-    }
-
-    /**
      * Provided as a convenience for agent implementations that need an opportunity
      * to do one-time initialization before the actual backup or restore operation
      * is begun with information about the calling user.
@@ -279,14 +273,33 @@
      *
      * @hide
      */
-    public void onCreate(UserHandle user, @OperationType int operationType) {
-        // TODO: Instantiate with the correct type using a parameter.
-        mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP);
-
+    public void onCreate(UserHandle user) {
         onCreate();
+    }
 
+    /**
+     * @deprecated Use {@link BackupAgent#onCreate(UserHandle, int, int)} instead.
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
         mUser = user;
-        mOperationType = operationType;
+        mBackupDestination = backupDestination;
+
+        onCreate(user);
+    }
+
+    /**
+    * @hide
+    */
+    public void onCreate(UserHandle user, @BackupDestination int backupDestination,
+            @OperationType int operationType) {
+        mUser = user;
+        mBackupDestination = backupDestination;
+        mLogger = new BackupRestoreEventLogger(operationType);
+
+        onCreate(user, backupDestination);
     }
 
     /**
@@ -433,7 +446,7 @@
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
-                mOperationType);
+                mBackupDestination);
         if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) {
             return;
         }
@@ -643,7 +656,7 @@
         if (includeMap == null || includeMap.size() == 0) {
             // Do entire sub-tree for the provided token.
             fullBackupFileTree(packageName, domainToken,
-                    FullBackup.getBackupScheme(this, mOperationType)
+                    FullBackup.getBackupScheme(this, mBackupDestination)
                             .tokenToDirectoryPath(domainToken),
                     filterSet, traversalExcludeSet, data);
         } else if (includeMap.get(domainToken) != null) {
@@ -815,7 +828,7 @@
                                             ArraySet<String> systemExcludes,
             FullBackupDataOutput output) {
         // Pull out the domain and set it aside to use when making the tarball.
-        String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+        String domainPath = FullBackup.getBackupScheme(this, mBackupDestination)
                 .tokenToDirectoryPath(domain);
         if (domainPath == null) {
             // Should never happen.
@@ -927,7 +940,7 @@
     }
 
     private boolean isFileEligibleForRestore(File destination) throws IOException {
-        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
+        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mBackupDestination);
         if (!bs.isFullRestoreEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(FullBackup.TAG_XML_PARSER,
@@ -1001,7 +1014,7 @@
                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
                 + " mtime=" + mtime);
 
-        basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+        basePath = FullBackup.getBackupScheme(this, mBackupDestination).tokenToDirectoryPath(
                 domain);
         if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
             mode = -1;  // < 0 is a token to skip attempting a chmod()
diff --git a/core/java/android/app/backup/BackupAnnotations.java b/core/java/android/app/backup/BackupAnnotations.java
new file mode 100644
index 0000000..d922861
--- /dev/null
+++ b/core/java/android/app/backup/BackupAnnotations.java
@@ -0,0 +1,62 @@
+/*
+ * 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.backup;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotations related to Android Backup&Restore.
+ *
+ * @hide
+ */
+public class BackupAnnotations {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            OperationType.UNKNOWN,
+            OperationType.BACKUP,
+            OperationType.RESTORE,
+    })
+    public @interface OperationType {
+        int UNKNOWN = -1;
+        int BACKUP = 0;
+        int RESTORE = 1;
+    }
+
+    /**
+     * Denotes where the backup data is going (e.g. to the cloud or directly to the other device)
+     * during backup or where it is coming from during restore.
+     *
+     * @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            BackupDestination.CLOUD,
+            BackupDestination.DEVICE_TRANSFER,
+            BackupDestination.ADB_BACKUP
+    })
+    public @interface BackupDestination {
+        // A cloud backup.
+        int CLOUD = 0;
+        // A device to device migration.
+        int DEVICE_TRANSFER = 1;
+        // An adb backup.
+        int ADB_BACKUP = 2;
+    }
+}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index d2c7972..378020f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -200,22 +200,6 @@
     @SystemApi
     public static final int ERROR_TRANSPORT_INVALID = -2;
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        OperationType.BACKUP,
-        OperationType.MIGRATION,
-        OperationType.ADB_BACKUP,
-    })
-    public @interface OperationType {
-        // A backup / restore to / from an off-device location, e.g. cloud.
-        int BACKUP = 0;
-        // A direct transfer to another device.
-        int MIGRATION = 1;
-        // Backup via adb, data saved on the host machine.
-        int ADB_BACKUP = 3;
-    }
-
     private Context mContext;
     @UnsupportedAppUsage
     private static IBackupManager sService;
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 68740cb..f892833 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -16,13 +16,13 @@
 
 package android.app.backup;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.util.Slog;
 
 import java.lang.annotation.Retention;
@@ -56,21 +56,6 @@
     public static final int DATA_TYPES_ALLOWED = 15;
 
     /**
-     * Operation types for which this logger can be used.
-     *
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            OperationType.BACKUP,
-            OperationType.RESTORE
-    })
-    @interface OperationType {
-        int BACKUP = 1;
-        int RESTORE = 2;
-    }
-
-    /**
      * Denotes that the annotated element identifies a data type as required by the logging methods
      * of {@code BackupRestoreEventLogger}
      */
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index bf9a9b0..6371871 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,10 +16,9 @@
 
 package android.app.backup;
 
-import static android.app.backup.BackupManager.OperationType;
-
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -123,20 +122,20 @@
 
     /**
      * Identify {@link BackupScheme} object by package and operation type
-     * (see {@link OperationType}) it corresponds to.
+     * (see {@link BackupDestination}) it corresponds to.
      */
     private static class BackupSchemeId {
         final String mPackageName;
-        @OperationType final int mOperationType;
+        @BackupDestination final int mBackupDestination;
 
-        BackupSchemeId(String packageName, @OperationType int operationType) {
+        BackupSchemeId(String packageName, @BackupDestination int backupDestination) {
             mPackageName = packageName;
-            mOperationType = operationType;
+            mBackupDestination = backupDestination;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mPackageName, mOperationType);
+            return Objects.hash(mPackageName, mBackupDestination);
         }
 
         @Override
@@ -149,7 +148,7 @@
             }
             BackupSchemeId that = (BackupSchemeId) object;
             return Objects.equals(mPackageName, that.mPackageName) &&
-                    Objects.equals(mOperationType, that.mOperationType);
+                    Objects.equals(mBackupDestination, that.mBackupDestination);
         }
     }
 
@@ -164,19 +163,20 @@
             new ArrayMap<>();
 
     static synchronized BackupScheme getBackupScheme(Context context,
-            @OperationType int operationType) {
-        BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
+            @BackupDestination int backupDestination) {
+        BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(),
+                backupDestination);
         BackupScheme backupSchemeForPackage =
                 kPackageBackupSchemeMap.get(backupSchemeId);
         if (backupSchemeForPackage == null) {
-            backupSchemeForPackage = new BackupScheme(context, operationType);
+            backupSchemeForPackage = new BackupScheme(context, backupDestination);
             kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
         }
         return backupSchemeForPackage;
     }
 
     public static BackupScheme getBackupSchemeForTest(Context context) {
-        BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
+        BackupScheme testing = new BackupScheme(context, BackupDestination.CLOUD);
         testing.mExcludes = new ArraySet();
         testing.mIncludes = new ArrayMap();
         return testing;
@@ -303,7 +303,7 @@
 
         final int mDataExtractionRules;
         final int mFullBackupContent;
-        @OperationType final int mOperationType;
+        @BackupDestination final int mBackupDestination;
         final PackageManager mPackageManager;
         final StorageManager mStorageManager;
         final String mPackageName;
@@ -426,12 +426,12 @@
          */
         ArraySet<PathWithRequiredFlags> mExcludes;
 
-        BackupScheme(Context context, @OperationType int operationType) {
+        BackupScheme(Context context, @BackupDestination int backupDestination) {
             ApplicationInfo applicationInfo = context.getApplicationInfo();
 
             mDataExtractionRules = applicationInfo.dataExtractionRulesRes;
             mFullBackupContent = applicationInfo.fullBackupContent;
-            mOperationType = operationType;
+            mBackupDestination = backupDestination;
             mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
             mPackageManager = context.getPackageManager();
             mPackageName = context.getPackageName();
@@ -568,7 +568,7 @@
                 }
 
                 try {
-                    parseSchemeForOperationType(mOperationType);
+                    parseSchemeForBackupDestination(mBackupDestination);
                 } catch (PackageManager.NameNotFoundException e) {
                     // Throw it as an IOException
                     throw new IOException(e);
@@ -576,12 +576,12 @@
             }
         }
 
-        private void parseSchemeForOperationType(@OperationType int operationType)
+        private void parseSchemeForBackupDestination(@BackupDestination int backupDestination)
                 throws PackageManager.NameNotFoundException, IOException, XmlPullParserException {
-            String configSection = getConfigSectionForOperationType(operationType);
+            String configSection = getConfigSectionForBackupDestination(backupDestination);
             if (configSection == null) {
-                Slog.w(TAG, "Given operation type isn't supported by backup scheme: "
-                        + operationType);
+                Slog.w(TAG, "Given backup destination isn't supported by backup scheme: "
+                        + backupDestination);
                 return;
             }
 
@@ -600,7 +600,7 @@
                 }
             }
 
-            if (operationType == OperationType.MIGRATION
+            if (backupDestination == BackupDestination.DEVICE_TRANSFER
                     && CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) {
                 mIsUsingNewScheme = true;
                 return;
@@ -615,11 +615,12 @@
         }
 
         @Nullable
-        private String getConfigSectionForOperationType(@OperationType int operationType)  {
-            switch (operationType) {
-                case OperationType.BACKUP:
+        private String getConfigSectionForBackupDestination(
+                @BackupDestination int backupDestination)  {
+            switch (backupDestination) {
+                case BackupDestination.CLOUD:
                     return ConfigSection.CLOUD_BACKUP;
-                case OperationType.MIGRATION:
+                case BackupDestination.DEVICE_TRANSFER:
                     return ConfigSection.DEVICE_TRANSFER;
                 default:
                     return null;
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
index 710b8c4..6b5e667 100644
--- a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -16,6 +16,8 @@
 
 package android.app.time;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
 import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
 import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
 import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
@@ -86,12 +88,24 @@
     public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4;
 
     /**
-     * An instance that provides no information about algorithm status because the algorithm has not
-     * yet reported. Effectively a "null" status placeholder.
+     * An instance used when the location algorithm is not supported by the device.
      */
-    @NonNull
-    public static final LocationTimeZoneAlgorithmStatus UNKNOWN =
-            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+    public static final LocationTimeZoneAlgorithmStatus NOT_SUPPORTED =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+                    PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+
+    /**
+     * An instance used when the location algorithm is running, but has not reported an event.
+     */
+    public static final LocationTimeZoneAlgorithmStatus RUNNING_NOT_REPORTED =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                    PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+    /**
+     * An instance used when the location algorithm is supported but not running.
+     */
+    public static final LocationTimeZoneAlgorithmStatus NOT_RUNNING =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
                     PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
 
     private final @DetectionAlgorithmStatus int mStatus;
@@ -306,6 +320,40 @@
                 mSecondaryProviderStatus, mSecondaryProviderReportedStatus);
     }
 
+    /**
+     * Returns {@code true} if the algorithm status could allow the time zone detector to enter
+     * telephony fallback mode.
+     */
+    public boolean couldEnableTelephonyFallback() {
+        if (mStatus == DETECTION_ALGORITHM_STATUS_UNKNOWN
+                || mStatus == DETECTION_ALGORITHM_STATUS_NOT_RUNNING
+                || mStatus == DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED) {
+            // This method is not expected to be called on objects with these statuses. Fallback
+            // should not be enabled if it is.
+            return false;
+        }
+
+        // mStatus == DETECTOR_STATUS_RUNNING.
+
+        boolean primarySuggestsFallback = false;
+        if (mPrimaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) {
+            primarySuggestsFallback = true;
+        } else if (mPrimaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN
+                && mPrimaryProviderReportedStatus != null) {
+            primarySuggestsFallback = mPrimaryProviderReportedStatus.couldEnableTelephonyFallback();
+        }
+
+        boolean secondarySuggestsFallback = false;
+        if (mSecondaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) {
+            secondarySuggestsFallback = true;
+        } else if (mSecondaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN
+                && mSecondaryProviderReportedStatus != null) {
+            secondarySuggestsFallback =
+                    mSecondaryProviderReportedStatus.couldEnableTelephonyFallback();
+        }
+        return primarySuggestsFallback && secondarySuggestsFallback;
+    }
+
     /** @hide */
     @VisibleForTesting
     @NonNull
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 295d69d..0837d85 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -19,6 +19,9 @@
 import android.app.PendingIntent;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.input.VirtualKeyEvent;
@@ -97,6 +100,24 @@
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
+     * Creates a virtual sensor, capable of injecting sensor events into the system.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
+
+    /**
+     * Removes the sensor corresponding to the given token from the system.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void unregisterSensor(IBinder token);
+
+    /**
+     * Sends an event to the virtual sensor corresponding to the given token.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
+
+    /**
      * Launches a pending intent on the given display that is owned by this virtual device.
      */
     void launchPendingIntent(
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index c14bb1b..01b42bf 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -22,12 +22,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
@@ -58,6 +61,7 @@
 import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.IntConsumer;
 
@@ -76,7 +80,8 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
     /**
      * The default device ID, which is the ID of the primary (non-virtual) device.
@@ -88,6 +93,26 @@
      */
     public static final int INVALID_DEVICE_ID = -1;
 
+    /**
+     * Broadcast Action: A Virtual Device was removed.
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VIRTUAL_DEVICE_REMOVED =
+            "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED";
+
+    /**
+     * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}.
+     * Contains the identifier of the virtual device, which was removed.
+     *
+     * @hide
+     */
+    public static final String EXTRA_VIRTUAL_DEVICE_ID =
+            "android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
@@ -250,7 +275,10 @@
                 };
         @Nullable
         private VirtualAudioDevice mVirtualAudioDevice;
+        @NonNull
+        private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
 
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         private VirtualDevice(
                 IVirtualDeviceManager service,
                 Context context,
@@ -264,6 +292,10 @@
                     associationId,
                     params,
                     mActivityListenerBinder);
+            final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
+            for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
+                mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
+            }
         }
 
         /**
@@ -278,6 +310,23 @@
         }
 
         /**
+         * Returns this device's sensor with the given type and name, if any.
+         *
+         * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
+         *
+         * @param type The type of the sensor.
+         * @param name The name of the sensor.
+         * @return The matching sensor if found, {@code null} otherwise.
+         */
+        @Nullable
+        public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
+            return mVirtualSensors.stream()
+                    .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
+                    .findAny()
+                    .orElse(null);
+        }
+
+        /**
          * Launches a given pending intent on the give display ID.
          *
          * @param displayId The display to launch the pending intent on. This display must be
@@ -350,14 +399,72 @@
                 @VirtualDisplayFlag int flags,
                 @Nullable @CallbackExecutor Executor executor,
                 @Nullable VirtualDisplay.Callback callback) {
-            // TODO(b/205343547): Handle display groups properly instead of creating a new display
-            //  group for every new virtual display created using this API.
-            // belongs to the same display group.
             VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
                     getVirtualDisplayName(), width, height, densityDpi)
                     .setSurface(surface)
                     .setFlags(getVirtualDisplayFlags(flags))
                     .build();
+            return createVirtualDisplayInternal(config, executor, callback);
+        }
+
+        /**
+         * Creates a virtual display for this virtual device. All displays created on the same
+         * device belongs to the same display group.
+         *
+         * @param width The width of the virtual display in pixels, must be greater than 0.
+         * @param height The height of the virtual display in pixels, must be greater than 0.
+         * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
+         * @param displayCategories The categories of the virtual display, indicating the type of
+         * activities allowed to run on the display. Activities can declare their type using
+         * {@link android.content.pm.ActivityInfo#targetDisplayCategory}.
+         * @param surface The surface to which the content of the virtual display should
+         * be rendered, or null if there is none initially. The surface can also be set later using
+         * {@link VirtualDisplay#setSurface(Surface)}.
+         * @param flags A combination of virtual display flags accepted by
+         * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
+         * automatically set for all virtual devices:
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+         * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+         * @param executor The executor on which {@code callback} will be invoked. This is ignored
+         * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
+         * not be null.
+         * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
+         * @return The newly created virtual display, or {@code null} if the application could
+         * not create the virtual display.
+         *
+         * @see DisplayManager#createVirtualDisplay
+         */
+        @Nullable
+        public VirtualDisplay createVirtualDisplay(
+                @IntRange(from = 1) int width,
+                @IntRange(from = 1) int height,
+                @IntRange(from = 1) int densityDpi,
+                @NonNull List<String> displayCategories,
+                @Nullable Surface surface,
+                @VirtualDisplayFlag int flags,
+                @Nullable @CallbackExecutor Executor executor,
+                @Nullable VirtualDisplay.Callback callback) {
+            VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+                    getVirtualDisplayName(), width, height, densityDpi)
+                    .setDisplayCategories(displayCategories)
+                    .setSurface(surface)
+                    .setFlags(getVirtualDisplayFlags(flags))
+                    .build();
+            return createVirtualDisplayInternal(config, executor, callback);
+        }
+
+        /**
+         * @hide
+         */
+        @Nullable
+        private VirtualDisplay createVirtualDisplayInternal(
+                @NonNull VirtualDisplayConfig config,
+                @Nullable @CallbackExecutor Executor executor,
+                @Nullable VirtualDisplay.Callback callback) {
+            // TODO(b/205343547): Handle display groups properly instead of creating a new display
+            //  group for every new virtual display created using this API.
+            // belongs to the same display group.
             IVirtualDisplayCallback callbackWrapper =
                     new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor);
             final int displayId;
@@ -379,6 +486,7 @@
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         public void close() {
             try {
+                // This also takes care of unregistering all virtual sensors.
                 mVirtualDevice.close();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -564,6 +672,28 @@
         }
 
         /**
+         * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
+         * internal use, since device sensors must remain valid for the entire lifetime of the
+         * device.
+         *
+         * @param config The configuration of the sensor.
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
+            Objects.requireNonNull(config);
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.sensor.VirtualSensor:" + config.getName());
+                mVirtualDevice.createVirtualSensor(token, config);
+                return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Adds an activity listener to listen for events such as top activity change or virtual
          * display task stack became empty.
          *
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f8c2e34a..bad26c6 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -23,20 +23,22 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -158,6 +160,7 @@
     @Nullable private final String mName;
     // Mapping of @PolicyType to @DevicePolicy
     @NonNull private final SparseIntArray mDevicePolicies;
+    @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -169,24 +172,22 @@
             @NonNull Set<ComponentName> blockedActivities,
             @ActivityPolicy int defaultActivityPolicy,
             @Nullable String name,
-            @NonNull SparseIntArray devicePolicies) {
-        Preconditions.checkNotNull(usersWithMatchingAccounts);
-        Preconditions.checkNotNull(allowedCrossTaskNavigations);
-        Preconditions.checkNotNull(blockedCrossTaskNavigations);
-        Preconditions.checkNotNull(allowedActivities);
-        Preconditions.checkNotNull(blockedActivities);
-        Preconditions.checkNotNull(devicePolicies);
-
+            @NonNull SparseIntArray devicePolicies,
+            @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
         mLockState = lockState;
-        mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
-        mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
-        mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
+        mUsersWithMatchingAccounts =
+                new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
+        mAllowedCrossTaskNavigations =
+                new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
+        mBlockedCrossTaskNavigations =
+                new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
         mDefaultNavigationPolicy = defaultNavigationPolicy;
-        mAllowedActivities = new ArraySet<>(allowedActivities);
-        mBlockedActivities = new ArraySet<>(blockedActivities);
+        mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
+        mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
         mDefaultActivityPolicy = defaultActivityPolicy;
         mName = name;
-        mDevicePolicies = devicePolicies;
+        mDevicePolicies = Objects.requireNonNull(devicePolicies);
+        mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
     }
 
     @SuppressWarnings("unchecked")
@@ -201,6 +202,8 @@
         mDefaultActivityPolicy = parcel.readInt();
         mName = parcel.readString8();
         mDevicePolicies = parcel.readSparseIntArray();
+        mVirtualSensorConfigs = new ArrayList<>();
+        parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
     }
 
     /**
@@ -316,6 +319,15 @@
         return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
     }
 
+    /**
+     * Returns the configurations for all sensors that should be created for this device.
+     *
+     * @see Builder#addVirtualSensorConfig
+     */
+    public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
+        return mVirtualSensorConfigs;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -333,6 +345,7 @@
         dest.writeInt(mDefaultActivityPolicy);
         dest.writeString8(mName);
         dest.writeSparseIntArray(mDevicePolicies);
+        dest.writeTypedList(mVirtualSensorConfigs);
     }
 
     @Override
@@ -428,6 +441,7 @@
         private boolean mDefaultActivityPolicyConfigured = false;
         @Nullable private String mName;
         @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+        @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -467,8 +481,7 @@
         @NonNull
         public Builder setUsersWithMatchingAccounts(
                 @NonNull Set<UserHandle> usersWithMatchingAccounts) {
-            Preconditions.checkNotNull(usersWithMatchingAccounts);
-            mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+            mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
             return this;
         }
 
@@ -491,7 +504,6 @@
         @NonNull
         public Builder setAllowedCrossTaskNavigations(
                 @NonNull Set<ComponentName> allowedCrossTaskNavigations) {
-            Preconditions.checkNotNull(allowedCrossTaskNavigations);
             if (mDefaultNavigationPolicyConfigured
                     && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
                 throw new IllegalArgumentException(
@@ -500,7 +512,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
             mDefaultNavigationPolicyConfigured = true;
-            mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
+            mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
             return this;
         }
 
@@ -523,7 +535,6 @@
         @NonNull
         public Builder setBlockedCrossTaskNavigations(
                 @NonNull Set<ComponentName> blockedCrossTaskNavigations) {
-            Preconditions.checkNotNull(blockedCrossTaskNavigations);
             if (mDefaultNavigationPolicyConfigured
                      && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
                 throw new IllegalArgumentException(
@@ -532,7 +543,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
             mDefaultNavigationPolicyConfigured = true;
-            mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
+            mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
             return this;
         }
 
@@ -551,7 +562,6 @@
          */
         @NonNull
         public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
-            Preconditions.checkNotNull(allowedActivities);
             if (mDefaultActivityPolicyConfigured
                     && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
                 throw new IllegalArgumentException(
@@ -559,7 +569,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
             mDefaultActivityPolicyConfigured = true;
-            mAllowedActivities = allowedActivities;
+            mAllowedActivities = Objects.requireNonNull(allowedActivities);
             return this;
         }
 
@@ -578,7 +588,6 @@
          */
         @NonNull
         public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
-            Preconditions.checkNotNull(blockedActivities);
             if (mDefaultActivityPolicyConfigured
                     && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
                 throw new IllegalArgumentException(
@@ -586,7 +595,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
             mDefaultActivityPolicyConfigured = true;
-            mBlockedActivities = blockedActivities;
+            mBlockedActivities = Objects.requireNonNull(blockedActivities);
             return this;
         }
 
@@ -621,10 +630,49 @@
         }
 
         /**
+         * Adds a configuration for a sensor that should be created for this virtual device.
+         *
+         * Device sensors must remain valid for the entire lifetime of the device, hence they are
+         * created together with the device itself, and removed when the device is removed.
+         *
+         * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
+         *
+         * @see android.companion.virtual.sensor.VirtualSensor
+         * @see #addDevicePolicy
+         */
+        @NonNull
+        public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
+            mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
+         *
+         * @throws IllegalArgumentException if there's mismatch between policy definition and
+         * the passed parameters or if there are sensor configs with the same type and name.
+         *
          */
         @NonNull
         public VirtualDeviceParams build() {
+            if (!mVirtualSensorConfigs.isEmpty()
+                    && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
+                            != DEVICE_POLICY_CUSTOM)) {
+                throw new IllegalArgumentException(
+                        "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+                                + "virtual sensors.");
+            }
+            SparseArray<Set<String>> sensorNameByType = new SparseArray();
+            for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
+                VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
+                Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
+                if (!sensorNames.add(config.getName())) {
+                    throw new IllegalArgumentException(
+                            "Sensor names must be unique for a particular sensor type.");
+                }
+                sensorNameByType.put(config.getType(), sensorNames);
+            }
+
             return new VirtualDeviceParams(
                     mLockState,
                     mUsersWithMatchingAccounts,
@@ -635,7 +683,8 @@
                     mBlockedActivities,
                     mDefaultActivityPolicy,
                     mName,
-                    mDevicePolicies);
+                    mDevicePolicies,
+                    mVirtualSensorConfigs);
         }
     }
 }
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
new file mode 100644
index 0000000..b99cc7e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.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 android.companion.virtual.sensor;
+
+/**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ *
+ * @hide
+ */
+oneway interface IVirtualSensorStateChangeCallback {
+
+    /**
+     * Called when the registered listeners to a virtual sensor have changed.
+     *
+     * @param enabled Whether the sensor is enabled.
+     * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
+     * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
+     * between the delivery of two batches of sensor events.
+     */
+    void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
new file mode 100644
index 0000000..a184481
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -0,0 +1,97 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.time.Duration;
+
+/**
+ * Representation of a sensor on a remote device, capable of sending events, such as an
+ * accelerometer or a gyroscope.
+ *
+ * This registers the sensor device with the sensor framework as a runtime sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualSensor {
+
+    /**
+     * Interface for notification of listener registration changes for a virtual sensor.
+     */
+    public interface SensorStateChangeCallback {
+        /**
+         * Called when the registered listeners to a virtual sensor have changed.
+         *
+         * @param enabled Whether the sensor is enabled.
+         * @param samplingPeriod The requested sampling period of the sensor.
+         * @param batchReportLatency The requested maximum time interval between the delivery of two
+         * batches of sensor events.
+         */
+        void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
+                @NonNull Duration batchReportLatency);
+    }
+
+    private final int mType;
+    private final String mName;
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /**
+     * @hide
+     */
+    public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
+        mType = type;
+        mName = name;
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    /**
+     * Returns the
+     * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the sensor.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Send a sensor event to the system.
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
+        try {
+            mVirtualDevice.sendSensorEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
similarity index 88%
copy from core/java/android/service/credentials/CreateCredentialResponse.aidl
copy to core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
index 73c9147..48b463a 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.credentials;
+package android.companion.virtual.sensor;
 
-parcelable CreateCredentialResponse;
+parcelable VirtualSensorConfig;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
new file mode 100644
index 0000000..7982fa5
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -0,0 +1,210 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration for creation of a virtual sensor.
+ * @see VirtualSensor
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorConfig implements Parcelable {
+
+    private final int mType;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mVendor;
+    @Nullable
+    private final IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+    private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+            @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
+        mType = type;
+        mName = name;
+        mVendor = vendor;
+        mStateChangeCallback = stateChangeCallback;
+    }
+
+    private VirtualSensorConfig(@NonNull Parcel parcel) {
+        mType = parcel.readInt();
+        mName = parcel.readString8();
+        mVendor = parcel.readString8();
+        mStateChangeCallback =
+                IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mType);
+        parcel.writeString8(mName);
+        parcel.writeString8(mVendor);
+        parcel.writeStrongBinder(
+                mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
+    }
+
+    /**
+     * Returns the
+     * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the sensor, which must be unique per sensor type for each virtual device.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the vendor string of the sensor.
+     * @see Builder#setVendor
+     */
+    @Nullable
+    public String getVendor() {
+        return mVendor;
+    }
+
+    /**
+     * Returns the callback to get notified about changes in the sensor listeners.
+     * @hide
+     */
+    @Nullable
+    public IVirtualSensorStateChangeCallback getStateChangeCallback() {
+        return mStateChangeCallback;
+    }
+
+    /**
+     * Builder for {@link VirtualSensorConfig}.
+     */
+    public static final class Builder {
+
+        private final int mType;
+        @NonNull
+        private final String mName;
+        @Nullable
+        private String mVendor;
+        @Nullable
+        private IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+        private static class SensorStateChangeCallbackDelegate
+                extends IVirtualSensorStateChangeCallback.Stub {
+            @NonNull
+            private final Executor mExecutor;
+            @NonNull
+            private final VirtualSensor.SensorStateChangeCallback mCallback;
+
+            SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
+                    @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+                mCallback = callback;
+                mExecutor = executor;
+            }
+            @Override
+            public void onStateChanged(boolean enabled, int samplingPeriodMicros,
+                    int batchReportLatencyMicros) {
+                final Duration samplingPeriod =
+                        Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
+                final Duration batchReportingLatency =
+                        Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
+                mExecutor.execute(() -> mCallback.onStateChanged(
+                        enabled, samplingPeriod, batchReportingLatency));
+            }
+        }
+
+        /**
+         * Creates a new builder.
+         *
+         * @param type The
+         * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+         * @param name The name of the sensor. Must be unique among all sensors with the same type
+         * that belong to the same virtual device.
+         */
+        public Builder(int type, @NonNull String name) {
+            mType = type;
+            mName = Objects.requireNonNull(name);
+        }
+
+        /**
+         * Creates a new {@link VirtualSensorConfig}.
+         */
+        @NonNull
+        public VirtualSensorConfig build() {
+            return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
+        }
+
+        /**
+         * Sets the vendor string of the sensor.
+         */
+        @NonNull
+        public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) {
+            mVendor = vendor;
+            return this;
+        }
+
+        /**
+         * Sets the callback to get notified about changes in the sensor listeners.
+         *
+         * @param executor The executor where the callback is executed on.
+         * @param callback The callback to get notified when the state of the sensor
+         * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public VirtualSensorConfig.Builder setStateChangeCallback(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+            mStateChangeCallback = new SensorStateChangeCallbackDelegate(
+                    Objects.requireNonNull(executor),
+                    Objects.requireNonNull(callback));
+            return this;
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VirtualSensorConfig> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualSensorConfig createFromParcel(Parcel source) {
+                    return new VirtualSensorConfig(source);
+                }
+
+                public VirtualSensorConfig[] newArray(int size) {
+                    return new VirtualSensorConfig[size];
+                }
+            };
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
similarity index 88%
copy from core/java/android/service/credentials/CreateCredentialResponse.aidl
copy to core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
index 73c9147..9943946 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.credentials;
+package android.companion.virtual.sensor;
 
-parcelable CreateCredentialResponse;
+parcelable VirtualSensorEvent;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
new file mode 100644
index 0000000..8f8860e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -0,0 +1,140 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+
+/**
+ * A sensor event that originated from a virtual device's sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorEvent implements Parcelable {
+
+    @NonNull
+    private float[] mValues;
+    private long mTimestampNanos;
+
+    private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) {
+        mValues = values;
+        mTimestampNanos = timestampNanos;
+    }
+
+    private VirtualSensorEvent(@NonNull Parcel parcel) {
+        final int valuesLength = parcel.readInt();
+        mValues = new float[valuesLength];
+        parcel.readFloatArray(mValues);
+        mTimestampNanos = parcel.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mValues.length);
+        parcel.writeFloatArray(mValues);
+        parcel.writeLong(mTimestampNanos);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the values of this sensor event. The length and contents depend on the
+     * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
+     * @see android.hardware.SensorEvent#values
+     */
+    @NonNull
+    public float[] getValues() {
+        return mValues;
+    }
+
+    /**
+     * The time in nanoseconds at which the event happened. For a given sensor, each new sensor
+     * event should be monotonically increasing.
+     *
+     * @see Builder#setTimestampNanos(long)
+     */
+    public long getTimestampNanos() {
+        return mTimestampNanos;
+    }
+
+    /**
+     * Builder for {@link VirtualSensorEvent}.
+     */
+    public static final class Builder {
+
+        @NonNull
+        private float[] mValues;
+        private long mTimestampNanos = 0;
+
+        /**
+         * Creates a new builder.
+         * @param values the values of the sensor event. @see android.hardware.SensorEvent#values
+         */
+        public Builder(@NonNull float[] values) {
+            mValues = values;
+        }
+
+        /**
+         * Creates a new {@link VirtualSensorEvent}.
+         */
+        @NonNull
+        public VirtualSensorEvent build() {
+            if (mValues == null || mValues.length == 0) {
+                throw new IllegalArgumentException(
+                        "Cannot build virtual sensor event with no values.");
+            }
+            if (mTimestampNanos <= 0) {
+                mTimestampNanos = SystemClock.elapsedRealtimeNanos();
+            }
+            return new VirtualSensorEvent(mValues, mTimestampNanos);
+        }
+
+        /**
+         * Sets the timestamp of this event. For a given sensor, each new sensor event should be
+         * monotonically increasing using the same time base as
+         * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
+         *
+         * If not explicitly set, the current timestamp is used for the sensor event.
+         *
+         * @see android.hardware.SensorEvent#timestamp
+         */
+        @NonNull
+        public Builder setTimestampNanos(long timestampNanos) {
+            mTimestampNanos = timestampNanos;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualSensorEvent createFromParcel(Parcel source) {
+                    return new VirtualSensorEvent(source);
+                }
+
+                public VirtualSensorEvent[] newArray(int size) {
+                    return new VirtualSensorEvent[size];
+                }
+            };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0b20078..9f9fd3c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3939,6 +3939,7 @@
             DISPLAY_HASH_SERVICE,
             CREDENTIAL_SERVICE,
             DEVICE_LOCK_SERVICE,
+            VIRTUALIZATION_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -6114,6 +6115,20 @@
     public static final String DEVICE_LOCK_SERVICE = "device_lock";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.system.virtualmachine.VirtualMachineManager}.
+     *
+     * <p>On devices without {@link PackageManager#FEATURE_VIRTUALIZATION_FRAMEWORK} system feature
+     * the {@link #getSystemService(String)} will return {@code null}.
+     *
+     * @see #getSystemService(String)
+     * @see android.system.virtualmachine.VirtualMachineManager
+     * @hide
+     */
+    @SystemApi
+    public static final String VIRTUALIZATION_SERVICE = "virtualization";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6186,7 +6201,7 @@
      */
     @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
     @PackageManager.PermissionResult
-    @PermissionMethod
+    @PermissionMethod(orSelf = true)
     public abstract int checkCallingOrSelfPermission(@NonNull @PermissionName String permission);
 
     /**
@@ -6254,7 +6269,7 @@
      *
      * @see #checkCallingOrSelfPermission(String)
      */
-    @PermissionMethod
+    @PermissionMethod(orSelf = true)
     public abstract void enforceCallingOrSelfPermission(
             @NonNull @PermissionName String permission, @Nullable String message);
 
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index cc7977a..99fc5a3 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,15 +16,21 @@
 
 package android.content.om;
 
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
+import android.util.TypedValue;
 
+import com.android.internal.content.om.OverlayManagerImpl;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -82,8 +88,24 @@
         }
 
         /**
+         * Constructs a builder for building a fabricated overlay.
+         *
+         * @param name a name used to uniquely identify the fabricated overlay owned by the caller
+         *             itself.
+         * @param targetPackage the name of the package to overlay
+         */
+        public Builder(@NonNull String name, @NonNull String targetPackage) {
+            mName = OverlayManagerImpl.checkOverlayNameValid(name);
+            mTargetPackage =
+                    Preconditions.checkStringNotEmpty(
+                            targetPackage, "'targetPackage' must not be empty nor null");
+            mOwningPackage = ""; // The package name is filled in OverlayManager.commit
+        }
+
+        /**
          * Sets the name of the overlayable resources to overlay (can be null).
          */
+        @NonNull
         public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
             mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
             return this;
@@ -111,45 +133,110 @@
         }
 
         /**
-         * Sets the value of the fabricated overlay
+         * Sets the value of the fabricated overlay for the integer-like types.
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param dataType the data type of the new value
          * @param value the unsigned 32 bit integer representing the new value
-         *
+         * @return the builder itself
+         * @see #setResourceValue(String, int, int, String)
          * @see android.util.TypedValue#type
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+                        int dataType,
+                int value) {
+            return setResourceValue(resourceName, dataType, value, null /* configuration */);
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the integer-like types with the
+         * configuration.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @see android.util.TypedValue#type
+         */
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+                        int dataType,
+                int value,
+                @Nullable String configuration) {
             ensureValidResourceName(resourceName);
 
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
+            entry.dataType =
+                    Preconditions.checkArgumentInRange(
+                            dataType,
+                            TypedValue.TYPE_FIRST_INT,
+                            TypedValue.TYPE_LAST_INT,
+                            "dataType");
             entry.data = value;
+            entry.configuration = configuration;
             mEntries.add(entry);
             return this;
         }
 
+        /** @hide */
+        @IntDef(
+                prefix = {"OVERLAY_TYPE"},
+                value = {
+                    TypedValue.TYPE_STRING,
+                })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface StringTypeOverlayResource {}
+
         /**
-         * Sets the value of the fabricated overlay
+         * Sets the value of the fabricated overlay for the string-like type.
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param dataType the data type of the new value
-         * @param value the unsigned 32 bit integer representing the new value
-         * @param configuration The string representation of the config this overlay is enabled for
-         *
+         * @param value the string representing the new value
+         * @return the builder itself
          * @see android.util.TypedValue#type
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
-                String configuration) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @StringTypeOverlayResource int dataType,
+                @NonNull String value) {
+            return setResourceValue(resourceName, dataType, value, null /* configuration */);
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the string-like type with the configuration.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the string representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @see android.util.TypedValue#type
+         */
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @StringTypeOverlayResource int dataType,
+                @NonNull String value,
+                @Nullable String configuration) {
             ensureValidResourceName(resourceName);
 
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.data = value;
+            entry.dataType =
+                    Preconditions.checkArgumentInRange(
+                            dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
+            entry.stringData = Objects.requireNonNull(value);
             entry.configuration = configuration;
             mEntries.add(entry);
             return this;
@@ -159,68 +246,32 @@
          * Sets the value of the fabricated overlay
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
-         * @param dataType the data type of the new value
-         * @param value the string representing the new value
-         *
-         * @see android.util.TypedValue#type
-         */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
-            ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.stringData = value;
-            mEntries.add(entry);
-            return this;
-        }
-
-        /**
-         * Sets the value of the fabricated overlay
-         *
-         * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
-         * @param dataType the data type of the new value
-         * @param value the string representing the new value
-         * @param configuration The string representation of the config this overlay is enabled for
-         *
-         * @see android.util.TypedValue#type
-         */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
-                String configuration) {
-            ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.stringData = value;
-            entry.configuration = configuration;
-            mEntries.add(entry);
-            return this;
-        }
-
-        /**
-         * Sets the value of the fabricated overlay
-         *
-         * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [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
+         * @return the builder itself
          */
-        public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
-                String configuration) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @NonNull ParcelFileDescriptor value,
+                @Nullable String configuration) {
             ensureValidResourceName(resourceName);
 
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.binaryData = value;
+            entry.binaryData = Objects.requireNonNull(value);
             entry.configuration = configuration;
             mEntries.add(entry);
             return this;
         }
 
-        /** Builds an immutable fabricated overlay. */
+        /**
+         * Builds an immutable fabricated overlay.
+         *
+         * @return the fabricated overlay
+         */
+        @NonNull
         public FabricatedOverlay build() {
             final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
             overlay.packageName = mOwningPackage;
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 94275ae..ed1f6a2 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -17,6 +17,7 @@
 package android.content.om;
 
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -25,12 +26,16 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.content.om.OverlayManagerImpl;
+
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -76,6 +81,7 @@
 
     private final IOverlayManager mService;
     private final Context mContext;
+    private final OverlayManagerImpl mOverlayManagerImpl;
 
     /**
      * Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e
@@ -92,6 +98,21 @@
     private static final long THROW_SECURITY_EXCEPTIONS = 147340954;
 
     /**
+     * Applications can use OverlayManager to create overlays to overlay on itself resources. The
+     * overlay target is itself and the work range is only in caller application.
+     *
+     * <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link
+     * java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is
+     * enabled, the caller application can get the OverlayManager instance to use self-targeting
+     * functionality.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long SELF_TARGETING_OVERLAY = 205919743;
+
+    /**
      * Creates a new instance.
      *
      * @param context The current context in which to operate.
@@ -102,6 +123,7 @@
     public OverlayManager(Context context, IOverlayManager service) {
         mContext = context;
         mService = service;
+        mOverlayManagerImpl = new OverlayManagerImpl(context);
     }
 
     /** @hide */
@@ -286,6 +308,17 @@
      * @hide
      */
     public void commit(@NonNull final OverlayManagerTransaction transaction) {
+        if (transaction.isSelfTargetingTransaction()
+                || mService == null
+                || mService.asBinder() == null) {
+            try {
+                commitSelfTarget(transaction);
+            } catch (PackageManager.NameNotFoundException | IOException e) {
+                throw new RuntimeException(e);
+            }
+            return;
+        }
+
         try {
             mService.commit(transaction);
         } catch (RemoteException e) {
@@ -317,4 +350,48 @@
             throw e;
         }
     }
+
+    /**
+     * Get a OverlayManagerTransaction.Builder to build out a overlay manager transaction.
+     *
+     * @return a builder of the overlay manager transaction.
+     * @hide
+     */
+    @NonNull
+    public OverlayManagerTransaction.Builder beginTransaction() {
+        return new OverlayManagerTransaction.Builder(this);
+    }
+
+    /**
+     * Commit the self-targeting transaction to register or unregister overlays.
+     *
+     * <p>Applications can request OverlayManager to register overlays and unregister the registered
+     * overlays via {@link OverlayManagerTransaction}.
+     *
+     * @throws IOException if there is a file operation error.
+     * @throws PackageManager.NameNotFoundException if the package name is not found.
+     * @hide
+     */
+    @NonUiContext
+    void commitSelfTarget(@NonNull final OverlayManagerTransaction transaction)
+            throws PackageManager.NameNotFoundException, IOException {
+        synchronized (mOverlayManagerImpl) {
+            mOverlayManagerImpl.commit(transaction);
+        }
+    }
+
+    /**
+     * Get the related information of overlays for {@code targetPackageName}.
+     *
+     * @param targetPackageName the target package name
+     * @return a list of overlay information
+     * @hide
+     */
+    @NonNull
+    @NonUiContext
+    public List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName) {
+        synchronized (mOverlayManagerImpl) {
+            return mOverlayManagerImpl.getOverlayInfosForTarget(targetPackageName);
+        }
+    }
 }
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 868dab2..42b3ef3 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,19 +20,22 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.annotation.Nullable;
-import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 /**
  * Container for a batch of requests to the OverlayManagerService.
@@ -53,13 +56,16 @@
     // TODO: remove @hide from this class when OverlayManager is added to the
     // SDK, but keep OverlayManagerTransaction.Request @hidden
     private final List<Request> mRequests;
+    private final OverlayManager mOverlayManager;
 
-    OverlayManagerTransaction(@NonNull final List<Request> requests) {
+    OverlayManagerTransaction(
+            @NonNull final List<Request> requests, @Nullable OverlayManager overlayManager) {
         checkNotNull(requests);
         if (requests.contains(null)) {
             throw new IllegalArgumentException("null request");
         }
         mRequests = requests;
+        mOverlayManager = overlayManager;
     }
 
     private OverlayManagerTransaction(@NonNull final Parcel source) {
@@ -72,6 +78,7 @@
             final Bundle extras = source.readBundle(null);
             mRequests.add(new Request(request, overlay, userId, extras));
         }
+        mOverlayManager = null;
     }
 
     @Override
@@ -156,6 +163,20 @@
      */
     public static class Builder {
         private final List<Request> mRequests = new ArrayList<>();
+        @Nullable private final OverlayManager mOverlayManager;
+
+        public Builder() {
+            mOverlayManager = null;
+        }
+
+        /**
+         * The transaction builder for self-targeting.
+         *
+         * @param overlayManager is not null if the transaction is for self-targeting.
+         */
+        Builder(@NonNull OverlayManager overlayManager) {
+            mOverlayManager = Objects.requireNonNull(overlayManager);
+        }
 
         /**
          * Request that an overlay package be enabled and change its loading
@@ -205,7 +226,10 @@
          *
          * @hide
          */
+        @NonNull
         public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+            Objects.requireNonNull(overlay);
+
             final Bundle extras = new Bundle();
             extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
             mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
@@ -220,7 +244,10 @@
          *
          * @hide
          */
+        @NonNull
         public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+            Objects.requireNonNull(overlay);
+
             mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
                     UserHandle.USER_ALL));
             return this;
@@ -233,8 +260,9 @@
          * @see OverlayManager#commit
          * @return a new transaction
          */
+        @NonNull
         public OverlayManagerTransaction build() {
-            return new OverlayManagerTransaction(mRequests);
+            return new OverlayManagerTransaction(mRequests, mOverlayManager);
         }
     }
 
@@ -269,4 +297,23 @@
             return new OverlayManagerTransaction[size];
         }
     };
+
+    /**
+     * Commit the overlay manager transaction to register or unregister overlays for self-targeting.
+     *
+     * <p>Applications can register overlays and unregister the registered overlays via {@link
+     * OverlayManagerTransaction}.
+     *
+     * @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 {
+        mOverlayManager.commitSelfTarget(this);
+    }
+
+    boolean isSelfTargetingTransaction() {
+        return mOverlayManager != null;
+    }
 }
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 12911d6..1e928bd 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
 import android.content.IntentSender;
+import android.os.RemoteCallback;
 
 import android.graphics.Bitmap;
 
@@ -66,4 +67,6 @@
 
     void setAllowUnlimitedSilentUpdates(String installerPackageName);
     void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
+    void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
+            in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
 }
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 1fc6bda..7d9c64a 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -61,4 +61,6 @@
     int getInstallFlags();
 
     void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
+
+    boolean isKeepApplicationEnabledSetting();
 }
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 833919e..ab9d4f3 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+parcelable PackageInstaller.InstallConstraints;
 parcelable PackageInstaller.SessionParams;
 parcelable PackageInstaller.SessionInfo;
 parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d7686e2..c79f99d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -36,6 +36,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
@@ -57,6 +58,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.ParcelableException;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -89,6 +91,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Offers the ability to install, upgrade, and remove applications on the
@@ -854,6 +857,29 @@
     }
 
     /**
+     * Check if install constraints are satisfied for the given packages.
+     *
+     * Note this query result is just a hint and subject to race because system states could
+     * change anytime in-between this query and committing the session.
+     *
+     * The result is returned by a callback because some constraints might take a long time
+     * to evaluate.
+     */
+    public void checkInstallConstraints(@NonNull List<String> packageNames,
+            @NonNull InstallConstraints constraints,
+            @NonNull Consumer<InstallConstraintsResult> callback) {
+        try {
+            var remoteCallback = new RemoteCallback(b -> {
+                callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+            });
+            mInstaller.checkInstallConstraints(
+                    mInstallerPackageName, packageNames, constraints, remoteCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Events for observing session lifecycle.
      * <p>
      * A typical session lifecycle looks like this:
@@ -1717,6 +1743,18 @@
                 e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * @return {@code true} if this session will keep the existing application enabled setting
+         * after installation.
+         */
+        public boolean isKeepApplicationEnabledSetting() {
+            try {
+                return mSession.isKeepApplicationEnabledSetting();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -1855,6 +1893,8 @@
         public boolean forceQueryableOverride;
         /** {@hide} */
         public int requireUserAction = USER_ACTION_UNSPECIFIED;
+        /** {@hide} */
+        public boolean keepApplicationEnabledSetting = false;
 
         /**
          * Construct parameters for a new package install session.
@@ -1899,6 +1939,7 @@
             rollbackDataPolicy = source.readInt();
             requireUserAction = source.readInt();
             packageSource = source.readInt();
+            keepApplicationEnabledSetting = source.readBoolean();
         }
 
         /** {@hide} */
@@ -1929,6 +1970,7 @@
             ret.rollbackDataPolicy = rollbackDataPolicy;
             ret.requireUserAction = requireUserAction;
             ret.packageSource = packageSource;
+            ret.keepApplicationEnabledSetting = keepApplicationEnabledSetting;
             return ret;
         }
 
@@ -2415,6 +2457,14 @@
             this.installScenario = installScenario;
         }
 
+        /**
+         * Request to keep the original application enabled setting. This will prevent the
+         * application from being enabled if it was previously in a disabled state.
+         */
+        public void setKeepApplicationEnabledSetting() {
+            this.keepApplicationEnabledSetting = true;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -2443,6 +2493,7 @@
             pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
             pw.printPair("dataLoaderParams", dataLoaderParams);
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+            pw.printPair("keepApplicationEnabledSetting", keepApplicationEnabledSetting);
             pw.println();
         }
 
@@ -2483,6 +2534,7 @@
             dest.writeInt(rollbackDataPolicy);
             dest.writeInt(requireUserAction);
             dest.writeInt(packageSource);
+            dest.writeBoolean(keepApplicationEnabledSetting);
         }
 
         public static final Parcelable.Creator<SessionParams>
@@ -2684,6 +2736,9 @@
         /** @hide */
         public boolean isPreapprovalRequested;
 
+        /** @hide */
+        public boolean keepApplicationEnabledSetting;
+
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public SessionInfo() {
@@ -2737,6 +2792,7 @@
             requireUserAction = source.readInt();
             installerUid = source.readInt();
             packageSource = source.readInt();
+            keepApplicationEnabledSetting = source.readBoolean();
         }
 
         /**
@@ -3268,6 +3324,14 @@
             return installerUid;
         }
 
+        /**
+         * Returns {@code true} if this session will keep the existing application enabled setting
+         * after installation.
+         */
+        public boolean isKeepApplicationEnabledSetting() {
+            return keepApplicationEnabledSetting;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -3317,6 +3381,7 @@
             dest.writeInt(requireUserAction);
             dest.writeInt(installerUid);
             dest.writeInt(packageSource);
+            dest.writeBoolean(keepApplicationEnabledSetting);
         }
 
         public static final Parcelable.Creator<SessionInfo>
@@ -3608,4 +3673,362 @@
         // End of generated code
 
     }
+
+    /**
+     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    public static final class InstallConstraintsResult implements Parcelable {
+        /**
+         * True if all constraints are satisfied.
+         */
+        private boolean mAllConstraintsSatisfied;
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new InstallConstraintsResult.
+         *
+         * @param allConstraintsSatisfied
+         *   True if all constraints are satisfied.
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public InstallConstraintsResult(
+                boolean allConstraintsSatisfied) {
+            this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        /**
+         * True if all constraints are satisfied.
+         */
+        @DataClass.Generated.Member
+        public boolean isAllConstraintsSatisfied() {
+            return mAllConstraintsSatisfied;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mAllConstraintsSatisfied) flg |= 0x1;
+            dest.writeByte(flg);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ InstallConstraintsResult(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            boolean allConstraintsSatisfied = (flg & 0x1) != 0;
+
+            this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR
+                = new Parcelable.Creator<InstallConstraintsResult>() {
+            @Override
+            public InstallConstraintsResult[] newArray(int size) {
+                return new InstallConstraintsResult[size];
+            }
+
+            @Override
+            public InstallConstraintsResult createFromParcel(@NonNull Parcel in) {
+                return new InstallConstraintsResult(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1668650523745L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "private  boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
+    /**
+     * A class to encapsulate constraints for installation.
+     *
+     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+     * specifies the conditions to check against for the packages in question. This can be used
+     * by app stores to deliver auto updates without disrupting the user experience (referred as
+     * gentle update) - for example, an app store might hold off updates when it find out the
+     * app to update is interacting with the user.
+     *
+     * Use {@link Builder} to create a new instance and call mutator methods to add constraints.
+     * If no mutators were called, default constraints will be generated which implies no
+     * constraints. It is recommended to use preset constraints which are useful in most
+     * cases.
+     *
+     * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE}
+     * for the system knows best how to do it. It will also benefits the installer as the
+     * platform evolves and add more constraints to improve the accuracy and efficiency of
+     * gentle update.
+     *
+     * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
+     * library or bounded service), the constraints will also be applied to Bar.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    public static final class InstallConstraints implements Parcelable {
+        /**
+         * Preset constraints suitable for gentle update.
+         */
+        @NonNull
+        public static final InstallConstraints GENTLE_UPDATE =
+                new Builder().requireAppNotInteracting().build();
+
+        private final boolean mRequireDeviceIdle;
+        private final boolean mRequireAppNotForeground;
+        private final boolean mRequireAppNotInteracting;
+        private final boolean mRequireAppNotTopVisible;
+        private final boolean mRequireNotInCall;
+
+        /**
+         * Builder class for constructing {@link InstallConstraints}.
+         */
+        public static final class Builder {
+            private boolean mRequireDeviceIdle;
+            private boolean mRequireAppNotForeground;
+            private boolean mRequireAppNotInteracting;
+            private boolean mRequireAppNotTopVisible;
+            private boolean mRequireNotInCall;
+
+            /**
+             * This constraint requires the device is idle.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireDeviceIdle() {
+                mRequireDeviceIdle = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not in the foreground.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotForeground() {
+                mRequireAppNotForeground = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not interacting with the user.
+             * User interaction includes:
+             * <ul>
+             *     <li>playing or recording audio/video</li>
+             *     <li>sending or receiving network data</li>
+             *     <li>being visible to the user</li>
+             * </ul>
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotInteracting() {
+                mRequireAppNotInteracting = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not top-visible to the user.
+             * A top-visible app is showing UI at the top of the screen that the user is
+             * interacting with.
+             *
+             * Note this constraint is a subset of {@link #requireAppNotForeground()}
+             * because a top-visible app is also a foreground app. This is also a subset
+             * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+             * with the user.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotTopVisible() {
+                mRequireAppNotTopVisible = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires there is no ongoing call in the device.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireNotInCall() {
+                mRequireNotInCall = true;
+                return this;
+            }
+
+            /**
+             * Builds a new {@link InstallConstraints} instance.
+             */
+            @NonNull
+            public InstallConstraints build() {
+                return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
+                        mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+            }
+        }
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new InstallConstraints.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public InstallConstraints(
+                boolean requireDeviceIdle,
+                boolean requireAppNotForeground,
+                boolean requireAppNotInteracting,
+                boolean requireAppNotTopVisible,
+                boolean requireNotInCall) {
+            this.mRequireDeviceIdle = requireDeviceIdle;
+            this.mRequireAppNotForeground = requireAppNotForeground;
+            this.mRequireAppNotInteracting = requireAppNotInteracting;
+            this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+            this.mRequireNotInCall = requireNotInCall;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireDeviceIdle() {
+            return mRequireDeviceIdle;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotForeground() {
+            return mRequireAppNotForeground;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotInteracting() {
+            return mRequireAppNotInteracting;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotTopVisible() {
+            return mRequireAppNotTopVisible;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireNotInCall() {
+            return mRequireNotInCall;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mRequireDeviceIdle) flg |= 0x1;
+            if (mRequireAppNotForeground) flg |= 0x2;
+            if (mRequireAppNotInteracting) flg |= 0x4;
+            if (mRequireAppNotTopVisible) flg |= 0x8;
+            if (mRequireNotInCall) flg |= 0x10;
+            dest.writeByte(flg);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ InstallConstraints(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            boolean requireDeviceIdle = (flg & 0x1) != 0;
+            boolean requireAppNotForeground = (flg & 0x2) != 0;
+            boolean requireAppNotInteracting = (flg & 0x4) != 0;
+            boolean requireAppNotTopVisible = (flg & 0x8) != 0;
+            boolean requireNotInCall = (flg & 0x10) != 0;
+
+            this.mRequireDeviceIdle = requireDeviceIdle;
+            this.mRequireAppNotForeground = requireAppNotForeground;
+            this.mRequireAppNotInteracting = requireAppNotInteracting;
+            this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+            this.mRequireNotInCall = requireNotInCall;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR
+                = new Parcelable.Creator<InstallConstraints>() {
+            @Override
+            public InstallConstraints[] newArray(int size) {
+                return new InstallConstraints[size];
+            }
+
+            @Override
+            public InstallConstraints createFromParcel(@NonNull Parcel in) {
+                return new InstallConstraints(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1668650523752L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 485d04d..88b5e02 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1959,6 +1959,14 @@
     public static final int INSTALL_FAILED_MISSING_SPLIT = -28;
 
     /**
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package targets a deprecated SDK version.
+     *
+     * @hide
+     */
+    public static final int INSTALL_FAILED_DEPRECATED_SDK_VERSION = -29;
+
+    /**
      * Installation parse return code: this is passed in the
      * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a
      * file, or does not end with the expected '.apk' extension.
@@ -9618,6 +9626,7 @@
             case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
             case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA";
             case INSTALL_FAILED_MISSING_SPLIT: return "INSTALL_FAILED_MISSING_SPLIT";
+            case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return "INSTALL_FAILED_DEPRECATED_SDK_VERSION";
             case INSTALL_FAILED_BAD_SIGNATURE: return "INSTALL_FAILED_BAD_SIGNATURE";
             case INSTALL_FAILED_WRONG_INSTALLED_VERSION: return "INSTALL_FAILED_WRONG_INSTALLED_VERSION";
             case INSTALL_FAILED_PROCESS_NOT_DEFINED: return "INSTALL_FAILED_PROCESS_NOT_DEFINED";
@@ -9675,6 +9684,7 @@
             case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
             case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
             case INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+            case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
             default: return PackageInstaller.STATUS_FAILURE;
         }
     }
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
index ba97342..647c696 100644
--- a/core/java/android/content/pm/PermissionMethod.java
+++ b/core/java/android/content/pm/PermissionMethod.java
@@ -33,4 +33,20 @@
  */
 @Retention(CLASS)
 @Target({METHOD})
-public @interface PermissionMethod {}
+public @interface PermissionMethod {
+    /**
+     * Hard-coded list of permissions checked by this method
+     */
+    @PermissionName String[] value() default {};
+    /**
+     * If true, the check passes if the caller
+     * has any ONE of the supplied permissions
+     */
+    boolean anyOf() default false;
+    /**
+     * Signifies that the permission check passes if
+     * the calling process OR the current process has
+     * the permission
+     */
+    boolean orSelf() default false;
+}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 3b7ed07..9e6cf62 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -308,7 +308,9 @@
      * permissions:
      * {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
      * {@link android.Manifest.permission#BODY_SENSORS},
-     * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+     * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS},
+     * or one of the {@code "android.permission.health.*"} permissions defined in the
+     * {@link android.healthconnect.HealthPermissions}.
      */
     @RequiresPermission(
             allOf = {
@@ -424,7 +426,7 @@
      *      android:name=".MySpecialForegroundService"
      *      android:foregroundServiceType="specialUse|foo"&gt;
      *      &lt;property
-     *          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE""
+     *          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
      *          android:value="foo"
      *      /&gt;
      * &lt;/service&gt;
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 7a5ac8e..143c00d 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -26,6 +26,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import dalvik.annotation.optimization.CriticalNative;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -459,7 +461,7 @@
     private static native @NonNull String nativeGetAssetPath(long ptr);
     private static native @NonNull String nativeGetDebugName(long ptr);
     private static native long nativeGetStringBlock(long ptr);
-    private static native boolean nativeIsUpToDate(long ptr);
+    @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
     private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
     private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
             String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 6ce2242..ce6e1c7 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -563,6 +563,9 @@
         if (applyToSize) {
             inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
             inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+
+            float fontScale = inoutDm.scaledDensity / inoutDm.density;
+            inoutDm.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale);
         }
     }
 
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
new file mode 100644
index 0000000..c7fdb16
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -0,0 +1,148 @@
+/*
+ * 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.content.res;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve.
+ *
+ * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
+ * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen.
+ *
+ * <p>The thinking here is that large fonts are already big enough to read, but we still want to
+ * scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
+ *
+ * @hide
+ */
+public class FontScaleConverter {
+    /**
+     * How close the given SP should be to a canonical SP in the array before they are considered
+     * the same for lookup purposes.
+     */
+    private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f;
+
+    @VisibleForTesting
+    final float[] mFromSpValues;
+    @VisibleForTesting
+    final float[] mToDpValues;
+
+    /**
+     * Creates a lookup table for the given conversions.
+     *
+     * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+     *
+     * <p>The arrays must be sorted ascending and monotonically increasing.
+     *
+     * @param fromSp array of dimensions in SP
+     * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+     *
+     * @throws IllegalArgumentException if the array lengths don't match or are empty
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
+        if (fromSp.length != toDp.length || fromSp.length == 0) {
+            throw new IllegalArgumentException("Array lengths must match and be nonzero");
+        }
+
+        mFromSpValues = fromSp;
+        mToDpValues = toDp;
+    }
+
+    /**
+     * Convert a dimension in "sp" to "dp" using the lookup table.
+     *
+     * @hide
+     */
+    public float convertSpToDp(float sp) {
+        final float spPositive = Math.abs(sp);
+        // TODO(b/247861374): find a match at a higher index?
+        final int spRounded = Math.round(spPositive);
+        final float sign = Math.signum(sp);
+        final int index = Arrays.binarySearch(mFromSpValues, spRounded);
+        if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) {
+            // exact match, return the matching dp
+            return sign * mToDpValues[index];
+        } else {
+            // must be a value in between index and index + 1: interpolate.
+            final int lowerIndex = -(index + 1) - 1;
+
+            final float startSp;
+            final float endSp;
+            final float startDp;
+            final float endDp;
+
+            if (lowerIndex >= mFromSpValues.length - 1) {
+                // It's past our lookup table. Determine the last elements' scaling factor and use.
+                startSp = mFromSpValues[mFromSpValues.length - 1];
+                startDp = mToDpValues[mFromSpValues.length - 1];
+
+                if (startSp == 0) return 0;
+
+                final float scalingFactor = startDp / startSp;
+                return sp * scalingFactor;
+            } else if (lowerIndex == -1) {
+                // It's smaller than the smallest value in our table. Interpolate from 0.
+                startSp = 0;
+                startDp = 0;
+                endSp = mFromSpValues[0];
+                endDp = mToDpValues[0];
+            } else {
+                startSp = mFromSpValues[lowerIndex];
+                endSp = mFromSpValues[lowerIndex + 1];
+                startDp = mToDpValues[lowerIndex];
+                endDp = mToDpValues[lowerIndex + 1];
+            }
+
+            return sign * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, spPositive);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FontScaleConverter)) return false;
+        FontScaleConverter that = (FontScaleConverter) o;
+        return Arrays.equals(mFromSpValues, that.mFromSpValues)
+                && Arrays.equals(mToDpValues, that.mToDpValues);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(mFromSpValues);
+        result = 31 * result + Arrays.hashCode(mToDpValues);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "FontScaleConverter{"
+                + "fromSpValues="
+                + Arrays.toString(mFromSpValues)
+                + ", toDpValues="
+                + Arrays.toString(mToDpValues)
+                + '}';
+    }
+}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
new file mode 100644
index 0000000..c77a372
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Stores lookup tables for creating {@link FontScaleConverter}s at various scales.
+ *
+ * @hide
+ */
+public class FontScaleConverterFactory {
+    private static final float SCALE_KEY_MULTIPLIER = 100f;
+
+    @VisibleForTesting
+    static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+
+    static {
+        // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
+        // manually tweaked for optimum readability.
+        put(
+                /* scaleKey= */ 1.15f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f,   23f, 27.6f, 34.5f,  115})
+        );
+
+        put(
+                /* scaleKey= */ 1.3f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {10.4f,   13f, 15.6f, 18.2f, 23.4f,   26f, 31.2f,   39f,  130})
+        );
+
+        put(
+                /* scaleKey= */ 1.5f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {  12f,   15f,   18f,   21f,   27f,   30f,   36f,   45f,  150})
+        );
+
+        put(
+                /* scaleKey= */ 1.8f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {14.4f,   18f, 21.6f, 25.2f, 32.4f,   36f, 43.2f,   54f,  180})
+        );
+
+        put(
+                /* scaleKey= */ 2f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {  16f,   20f,   24f,   28f,   36f,   40f,   48f,   60f,  200})
+        );
+
+    }
+
+    private FontScaleConverterFactory() {}
+
+    /**
+     * Finds a matching FontScaleConverter for the given fontScale factor.
+     *
+     * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+     *
+     * @return a converter for the given scale, or null if non-linear scaling should not be used.
+     *
+     * @hide
+     */
+    @Nullable
+    public static FontScaleConverter forScale(float fontScale) {
+        if (fontScale <= 1) {
+            // We don't need non-linear curves for shrinking text or for 100%.
+            // Also, fontScale==0 should not have a curve either
+            return null;
+        }
+
+        FontScaleConverter lookupTable = get(fontScale);
+        // TODO(b/247861716): interpolate between two tables when null
+
+        return lookupTable;
+    }
+
+    private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
+        LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter);
+    }
+
+    @Nullable
+    private static FontScaleConverter get(float scaleKey) {
+        return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER));
+    }
+}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 09d24d4..c2b3769 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -434,6 +434,8 @@
                 // Protect against an unset fontScale.
                 mMetrics.scaledDensity = mMetrics.density *
                         (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
+                mMetrics.fontScaleConverter =
+                        FontScaleConverterFactory.forScale(mConfiguration.fontScale);
 
                 final int width, height;
                 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/credentials/ClearCredentialStateRequest.aidl
similarity index 81%
copy from core/java/android/service/credentials/CreateCredentialResponse.aidl
copy to core/java/android/credentials/ClearCredentialStateRequest.aidl
index 73c9147..2679ee4 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/core/java/android/credentials/ClearCredentialStateRequest.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.credentials;
+package android.credentials;
 
-parcelable CreateCredentialResponse;
+parcelable ClearCredentialStateRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/ClearCredentialStateRequest.java b/core/java/android/credentials/ClearCredentialStateRequest.java
new file mode 100644
index 0000000..33afbed
--- /dev/null
+++ b/core/java/android/credentials/ClearCredentialStateRequest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request class for clearing a user's credential state from the credential providers.
+ */
+public final class ClearCredentialStateRequest implements Parcelable {
+
+    /** The request data. */
+    @NonNull
+    private final Bundle mData;
+
+    /** Returns the request data. */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBundle(mData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "ClearCredentialStateRequest {data=" + mData + "}";
+    }
+
+    /**
+     * Constructs a {@link ClearCredentialStateRequest}.
+     *
+     * @param data the request data
+     */
+    public ClearCredentialStateRequest(@NonNull Bundle data) {
+        mData = requireNonNull(data, "data must not be null");
+    }
+
+    private ClearCredentialStateRequest(@NonNull Parcel in) {
+        Bundle data = in.readBundle();
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+    }
+
+    public static final @NonNull Creator<ClearCredentialStateRequest> CREATOR =
+            new Creator<ClearCredentialStateRequest>() {
+        @Override
+        public ClearCredentialStateRequest[] newArray(int size) {
+            return new ClearCredentialStateRequest[size];
+        }
+
+        @Override
+        public ClearCredentialStateRequest createFromParcel(@NonNull Parcel in) {
+            return new ClearCredentialStateRequest(in);
+        }
+    };
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 04d57ad..1efac6c 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.IntentSender;
@@ -65,17 +66,20 @@
      * credential, display a picker when multiple credentials exist, etc.
      *
      * @param request the request specifying type(s) of credentials to get from the user
+     * @param activity the activity used to launch any UI needed
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this {@link Executor}
      * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeGetCredential(
             @NonNull GetCredentialRequest request,
+            @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<
                     GetCredentialResponse, CredentialManagerException> callback) {
         requireNonNull(request, "request must not be null");
+        requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
         requireNonNull(callback, "callback must not be null");
 
@@ -88,8 +92,7 @@
         try {
             cancelRemote = mService.executeGetCredential(
                     request,
-                    // TODO: use a real activity instead of context.
-                    new GetCredentialTransport(mContext, executor, callback),
+                    new GetCredentialTransport(activity, executor, callback),
                     mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -107,17 +110,20 @@
      * or storing the new credential, etc.
      *
      * @param request the request specifying type(s) of credentials to get from the user
+     * @param activity the activity used to launch any UI needed
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this {@link Executor}
      * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeCreateCredential(
             @NonNull CreateCredentialRequest request,
+            @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<
                     CreateCredentialResponse, CredentialManagerException> callback) {
         requireNonNull(request, "request must not be null");
+        requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
         requireNonNull(callback, "callback must not be null");
 
@@ -129,8 +135,7 @@
         ICancellationSignal cancelRemote = null;
         try {
             cancelRemote = mService.executeCreateCredential(request,
-                    // TODO: use a real activity instead of context.
-                    new CreateCredentialTransport(mContext, executor, callback),
+                    new CreateCredentialTransport(activity, executor, callback),
                     mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -142,18 +147,24 @@
     }
 
     /**
-     * Clears the current user credential session from all credential providers.
+     * Clears the current user credential state from all credential providers.
      *
-     * <p>Usually invoked after your user signs out of your app so that they will not be
-     * automatically signed in the next time.
+     * You should invoked this api after your user signs out of your app to notify all credential
+     * providers that any stored credential session for the given app should be cleared.
      *
+     * A credential provider may have stored an active credential session and use it to limit
+     * sign-in options for future get-credential calls. For example, it may prioritize the active
+     * credential over any other available credential. When your user explicitly signs out of your
+     * app and in order to get the holistic sign-in options the next time, you should call this API
+     * to let the provider clear any stored credential session.
+     *
+     * @param request the request data
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this {@link Executor}
      * @param callback the callback invoked when the request succeeds or fails
-     *
-     * @hide
      */
-    public void clearCredentialSession(
+    public void clearCredentialState(
+            @NonNull ClearCredentialStateRequest request,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
@@ -167,8 +178,8 @@
 
         ICancellationSignal cancelRemote = null;
         try {
-            cancelRemote = mService.clearCredentialSession(
-                    new ClearCredentialSessionTransport(executor, callback),
+            cancelRemote = mService.clearCredentialState(request,
+                    new ClearCredentialStateTransport(executor, callback),
                     mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -182,14 +193,14 @@
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
-        private final Context mActivityContext;
+        private final Activity mActivity;
         private final Executor mExecutor;
         private final OutcomeReceiver<
                 GetCredentialResponse, CredentialManagerException> mCallback;
 
-        private GetCredentialTransport(Context activityContext, Executor executor,
+        private GetCredentialTransport(Activity activity, Executor executor,
                 OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
-            mActivityContext = activityContext;
+            mActivity = activity;
             mExecutor = executor;
             mCallback = callback;
         }
@@ -197,7 +208,7 @@
         @Override
         public void onPendingIntent(PendingIntent pendingIntent) {
             try {
-                mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+                mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
             } catch (IntentSender.SendIntentException e) {
                 Log.e(TAG, "startIntentSender() failed for intent:"
                         + pendingIntent.getIntentSender(), e);
@@ -220,14 +231,14 @@
     private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
-        private final Context mActivityContext;
+        private final Activity mActivity;
         private final Executor mExecutor;
         private final OutcomeReceiver<
                 CreateCredentialResponse, CredentialManagerException> mCallback;
 
-        private CreateCredentialTransport(Context activityContext, Executor executor,
+        private CreateCredentialTransport(Activity activity, Executor executor,
                 OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
-            mActivityContext = activityContext;
+            mActivity = activity;
             mExecutor = executor;
             mCallback = callback;
         }
@@ -235,7 +246,7 @@
         @Override
         public void onPendingIntent(PendingIntent pendingIntent) {
             try {
-                mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+                mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
             } catch (IntentSender.SendIntentException e) {
                 Log.e(TAG, "startIntentSender() failed for intent:"
                         + pendingIntent.getIntentSender(), e);
@@ -255,14 +266,14 @@
         }
     }
 
-    private static class ClearCredentialSessionTransport
-            extends IClearCredentialSessionCallback.Stub {
+    private static class ClearCredentialStateTransport
+            extends IClearCredentialStateCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
         private final Executor mExecutor;
         private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
 
-        private ClearCredentialSessionTransport(Executor executor,
+        private ClearCredentialStateTransport(Executor executor,
                 OutcomeReceiver<Void, CredentialManagerException> callback) {
             mExecutor = executor;
             mCallback = callback;
diff --git a/core/java/android/credentials/IClearCredentialSessionCallback.aidl b/core/java/android/credentials/IClearCredentialStateCallback.aidl
similarity index 88%
rename from core/java/android/credentials/IClearCredentialSessionCallback.aidl
rename to core/java/android/credentials/IClearCredentialStateCallback.aidl
index 903e7f5..f8b7ae44 100644
--- a/core/java/android/credentials/IClearCredentialSessionCallback.aidl
+++ b/core/java/android/credentials/IClearCredentialStateCallback.aidl
@@ -17,11 +17,11 @@
 package android.credentials;
 
 /**
- * Listener for clearCredentialSession request.
+ * Listener for clearCredentialState request.
  *
  * @hide
  */
-interface IClearCredentialSessionCallback {
+interface IClearCredentialStateCallback {
     oneway void onSuccess();
     oneway void onError(int errorCode, String message);
 }
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 35688d7..c5497bd 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -16,9 +16,10 @@
 
 package android.credentials;
 
+import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
-import android.credentials.IClearCredentialSessionCallback;
+import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
 import android.os.ICancellationSignal;
@@ -34,5 +35,5 @@
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
-    @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
+    @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1c4898a..18118f5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,8 +16,14 @@
 
 package android.hardware;
 
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -45,6 +51,7 @@
 import java.io.UncheckedIOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -80,6 +87,8 @@
     private static native boolean nativeGetSensorAtIndex(long nativeInstance,
             Sensor sensor, int index);
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
+    private static native void nativeGetRuntimeSensors(
+            long nativeInstance, int deviceId, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
@@ -100,6 +109,10 @@
 
     private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
     private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
+    private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>();
+    private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType =
+            new SparseArray<>();
+
     private boolean mDynamicSensorListDirty = true;
 
     private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
@@ -114,6 +127,7 @@
     private HashMap<DynamicSensorCallback, Handler>
             mDynamicSensorCallbacks = new HashMap<>();
     private BroadcastReceiver mDynamicSensorBroadcastReceiver;
+    private BroadcastReceiver mRuntimeSensorBroadcastReceiver;
 
     // Looper associated with the context in which this instance was created.
     private final Looper mMainLooper;
@@ -121,6 +135,7 @@
     private final boolean mIsPackageDebuggable;
     private final Context mContext;
     private final long mNativeInstance;
+    private final VirtualDeviceManager mVdm;
 
     private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
 
@@ -139,6 +154,7 @@
         mContext = context;
         mNativeInstance = nativeCreate(context.getOpPackageName());
         mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+        mVdm = mContext.getSystemService(VirtualDeviceManager.class);
 
         // initialize the sensor list
         for (int index = 0;; ++index) {
@@ -147,12 +163,63 @@
             mFullSensorsList.add(sensor);
             mHandleToSensor.put(sensor.getHandle(), sensor);
         }
+
+    }
+
+    /** @hide */
+    @Override
+    public List<Sensor> getSensorList(int type) {
+        final int deviceId = mContext.getDeviceId();
+        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+            return super.getSensorList(type);
+        }
+
+        // Cache the per-device lists on demand.
+        List<Sensor> list;
+        synchronized (mFullRuntimeSensorListByDevice) {
+            List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+            if (fullList == null) {
+                fullList = createRuntimeSensorListLocked(deviceId);
+            }
+            SparseArray<List<Sensor>> deviceSensorListByType =
+                    mRuntimeSensorListByDeviceByType.get(deviceId);
+            list = deviceSensorListByType.get(type);
+            if (list == null) {
+                if (type == Sensor.TYPE_ALL) {
+                    list = fullList;
+                } else {
+                    list = new ArrayList<>();
+                    for (Sensor i : fullList) {
+                        if (i.getType() == type) {
+                            list.add(i);
+                        }
+                    }
+                }
+                list = Collections.unmodifiableList(list);
+                deviceSensorListByType.append(type, list);
+            }
+        }
+        return list;
     }
 
     /** @hide */
     @Override
     protected List<Sensor> getFullSensorList() {
-        return mFullSensorsList;
+        final int deviceId = mContext.getDeviceId();
+        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+            return mFullSensorsList;
+        }
+
+        List<Sensor> fullList;
+        synchronized (mFullRuntimeSensorListByDevice) {
+            fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+            if (fullList == null) {
+                fullList = createRuntimeSensorListLocked(deviceId);
+            }
+        }
+        return fullList;
     }
 
     /** @hide */
@@ -446,12 +513,53 @@
         }
     }
 
+    private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
+        setupRuntimeSensorBroadcastReceiver();
+        List<Sensor> list = new ArrayList<>();
+        nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
+        mFullRuntimeSensorListByDevice.put(deviceId, list);
+        mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>());
+        for (Sensor s : list) {
+            mHandleToSensor.put(s.getHandle(), s);
+        }
+        return list;
+    }
+
+    private void setupRuntimeSensorBroadcastReceiver() {
+        if (mRuntimeSensorBroadcastReceiver == null) {
+            mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
+                        synchronized (mFullRuntimeSensorListByDevice) {
+                            final int deviceId = intent.getIntExtra(
+                                    EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
+                            List<Sensor> removedSensors =
+                                    mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
+                            if (removedSensors != null) {
+                                for (Sensor s : removedSensors) {
+                                    cleanupSensorConnection(s);
+                                }
+                            }
+                            mRuntimeSensorListByDeviceByType.remove(deviceId);
+                        }
+                    }
+                }
+            };
+
+            IntentFilter filter = new IntentFilter("virtual_device_removed");
+            filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
+            mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
+                    Context.RECEIVER_NOT_EXPORTED);
+        }
+    }
+
     private void setupDynamicSensorBroadcastReceiver() {
         if (mDynamicSensorBroadcastReceiver == null) {
             mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
-                    if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
+                    if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
                         if (DEBUG_DYNAMIC_SENSOR) {
                             Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
                         }
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 50551fee..f858227 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -245,11 +245,13 @@
      * applications is not guaranteed to be supported, however.</p>
      *
      * <p>For concurrent operation, in chronological order :
-     * - Applications must first close any open cameras that have sessions configured, using
-     *   {@link CameraDevice#close}.
-     * - All camera devices intended to be operated concurrently, must be opened using
-     *   {@link #openCamera}, before configuring sessions on any of the camera devices.</p>
-     *
+     * <ul>
+     * <li> Applications must first close any open cameras that have sessions configured, using
+     *   {@link CameraDevice#close}. </li>
+     * <li> All camera devices intended to be operated concurrently, must be opened using
+     *   {@link #openCamera}, before configuring sessions on any of the camera devices.</li>
+     *</ul>
+     *</p>
      * <p>Each device in a combination, is guaranteed to support stream combinations which may be
      * obtained by querying {@link #getCameraCharacteristics} for the key
      * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p>
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 9b07d3a..441fd88 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -137,7 +137,8 @@
             VIRTUAL_DISPLAY_FLAG_TRUSTED,
             VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
             VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
-            VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
+            VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
+            VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface VirtualDisplayFlag {}
@@ -403,6 +404,22 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13;
 
+    /**
+     * Virtual display flags: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #createVirtualDisplay
+     * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
+     * @hide
+     */
+    @TestApi
+    public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+
+
     /** @hide */
     @IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
             MATCH_CONTENT_FRAMERATE_UNKNOWN,
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index b76b98d..891ba36 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -30,6 +30,9 @@
 
 import com.android.internal.util.DataClass;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Holds configuration used to create {@link VirtualDisplay} instances. See
  * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}.
@@ -99,6 +102,13 @@
      */
     private boolean mWindowManagerMirroring = false;
 
+    /**
+     * The display categories. If set, only corresponding activities from the same category can be
+     * shown on the display.
+     */
+    @DataClass.PluralOf("displayCategory")
+    @NonNull private List<String> mDisplayCategories = new ArrayList<>();
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -124,7 +134,8 @@
             @Nullable Surface surface,
             @Nullable String uniqueId,
             int displayIdToMirror,
-            boolean windowManagerMirroring) {
+            boolean windowManagerMirroring,
+            @NonNull List<String> displayCategories) {
         this.mName = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mName);
@@ -147,6 +158,9 @@
         this.mUniqueId = uniqueId;
         this.mDisplayIdToMirror = displayIdToMirror;
         this.mWindowManagerMirroring = windowManagerMirroring;
+        this.mDisplayCategories = displayCategories;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDisplayCategories);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -233,6 +247,15 @@
         return mWindowManagerMirroring;
     }
 
+    /**
+     * The display categories. If set, only corresponding activities from the same category can be
+     * shown on the display.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getDisplayCategories() {
+        return mDisplayCategories;
+    }
+
     @Override
     @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -252,6 +275,7 @@
         if (mSurface != null) dest.writeTypedObject(mSurface, flags);
         if (mUniqueId != null) dest.writeString(mUniqueId);
         dest.writeInt(mDisplayIdToMirror);
+        dest.writeStringList(mDisplayCategories);
     }
 
     @Override
@@ -275,6 +299,8 @@
         Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
         String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
         int displayIdToMirror = in.readInt();
+        List<String> displayCategories = new ArrayList<>();
+        in.readStringList(displayCategories);
 
         this.mName = name;
         com.android.internal.util.AnnotationValidations.validate(
@@ -298,6 +324,9 @@
         this.mUniqueId = uniqueId;
         this.mDisplayIdToMirror = displayIdToMirror;
         this.mWindowManagerMirroring = windowManagerMirroring;
+        this.mDisplayCategories = displayCategories;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDisplayCategories);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -332,6 +361,7 @@
         private @Nullable String mUniqueId;
         private int mDisplayIdToMirror;
         private boolean mWindowManagerMirroring;
+        private @NonNull List<String> mDisplayCategories;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -478,10 +508,30 @@
             return this;
         }
 
+        /**
+         * The display categories. If set, only corresponding activities from the same category can be
+         * shown on the display.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDisplayCategories(@NonNull List<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x200;
+            mDisplayCategories = value;
+            return this;
+        }
+
+        /** @see #setDisplayCategories */
+        @DataClass.Generated.Member
+        public @NonNull Builder addDisplayCategory(@NonNull String value) {
+            if (mDisplayCategories == null) setDisplayCategories(new ArrayList<>());
+            mDisplayCategories.add(value);
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull VirtualDisplayConfig build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x200; // Mark builder used
+            mBuilderFieldsSet |= 0x400; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mFlags = 0;
@@ -498,6 +548,9 @@
             if ((mBuilderFieldsSet & 0x100) == 0) {
                 mWindowManagerMirroring = false;
             }
+            if ((mBuilderFieldsSet & 0x200) == 0) {
+                mDisplayCategories = new ArrayList<>();
+            }
             VirtualDisplayConfig o = new VirtualDisplayConfig(
                     mName,
                     mWidth,
@@ -507,12 +560,13 @@
                     mSurface,
                     mUniqueId,
                     mDisplayIdToMirror,
-                    mWindowManagerMirroring);
+                    mWindowManagerMirroring,
+                    mDisplayCategories);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x200) != 0) {
+            if ((mBuilderFieldsSet & 0x400) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -520,10 +574,10 @@
     }
 
     @DataClass.Generated(
-            time = 1646227247934L,
+            time = 1668534501320L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 96773f8..b0b7a41 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -398,6 +398,30 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface RoutingControl {}
 
+    // -- Whether the Soundbar mode feature is enabled or disabled.
+    /**
+     * Soundbar mode feature enabled.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    public static final int SOUNDBAR_MODE_ENABLED = 1;
+    /**
+     * Soundbar mode feature disabled.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    public static final int SOUNDBAR_MODE_DISABLED = 0;
+    /**
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     * @hide
+     */
+    @IntDef(prefix = { "SOUNDBAR_MODE" }, value = {
+            SOUNDBAR_MODE_ENABLED,
+            SOUNDBAR_MODE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SoundbarMode {}
+
     // -- Scope of CEC power control messages sent by a playback device.
     /**
      * Send CEC power control messages to TV only:
@@ -820,6 +844,14 @@
      */
     public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
     /**
+     * Name of a setting deciding whether the Soundbar mode feature is enabled.
+     * Before exposing this setting make sure the hardware supports it, otherwise, you may
+     * experience multiple issues.
+     *
+     * @see HdmiControlManager#setSoundbarMode(int)
+     */
+    public static final String CEC_SETTING_NAME_SOUNDBAR_MODE = "soundbar_mode";
+    /**
      * Name of a setting deciding on the power control mode.
      *
      * @see HdmiControlManager#setPowerControlMode(String)
@@ -1070,6 +1102,8 @@
     @StringDef(value = {
         CEC_SETTING_NAME_HDMI_CEC_ENABLED,
         CEC_SETTING_NAME_HDMI_CEC_VERSION,
+        CEC_SETTING_NAME_ROUTING_CONTROL,
+        CEC_SETTING_NAME_SOUNDBAR_MODE,
         CEC_SETTING_NAME_POWER_CONTROL_MODE,
         CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
         CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -1222,7 +1256,16 @@
             case HdmiDeviceInfo.DEVICE_PLAYBACK:
                 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
             case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
-                return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
+                try {
+                    if ((mService.getCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE)
+                            == SOUNDBAR_MODE_ENABLED && mHasPlaybackDevice)
+                            || mHasAudioSystemDevice) {
+                        return new HdmiAudioSystemClient(mService);
+                    }
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                return null;
             case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
                 return (mHasSwitchDevice || mIsSwitchDevice)
                     ? new HdmiSwitchClient(mService) : null;
@@ -2290,6 +2333,54 @@
     }
 
     /**
+     * Set the status of Soundbar mode feature.
+     *
+     * <p>This allows to enable/disable Soundbar mode on the playback device.
+     * The setting's effect will be available on devices where the hardware supports this feature.
+     * If enabled, an audio system local device will be allocated and try to establish an ARC
+     * connection with the TV. If disabled, the ARC connection will be terminated and the audio
+     * system local device will be removed from the network.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void setSoundbarMode(@SoundbarMode int value) {
+        if (mService == null) {
+            Log.e(TAG, "setSoundbarMode: HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            mService.setCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current status of Soundbar mode feature.
+     *
+     * <p>Reflects whether Soundbar mode is currently enabled on the playback device.
+     * If enabled, an audio system local device will be allocated and try to establish an ARC
+     * connection with the TV. If disabled, the ARC connection will be terminated and the audio
+     * system local device will be removed from the network.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    @SoundbarMode
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public int getSoundbarMode() {
+        if (mService == null) {
+            Log.e(TAG, "getSoundbarMode: HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            return mService.getCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Set the status of Power Control.
      *
      * <p>Specifies to which devices Power Control messages should be sent:
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index fd3d1ac..bdcbcaa 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -80,14 +80,30 @@
 
     // Keyboard layouts configuration.
     KeyboardLayout[] getKeyboardLayouts();
+
     KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
+
     KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
+
     String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
     void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
+
     String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
     void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
     void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
 
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index ade9fd6..b2dfd85 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.util.ArrayMap;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -41,14 +43,25 @@
 public final class ProgramList implements AutoCloseable {
 
     private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
             new ArrayMap<>();
 
+    @GuardedBy("mLock")
     private final List<ListCallback> mListCallbacks = new ArrayList<>();
+
+    @GuardedBy("mLock")
     private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+
+    @GuardedBy("mLock")
     private OnCloseListener mOnCloseListener;
-    private boolean mIsClosed = false;
-    private boolean mIsComplete = false;
+
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
+
+    @GuardedBy("mLock")
+    private boolean mIsComplete;
 
     ProgramList() {}
 
@@ -227,6 +240,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void putLocked(RadioManager.ProgramInfo value,
             List<ProgramSelector.Identifier> changedIdentifierList) {
         ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
@@ -235,6 +249,7 @@
         changedIdentifierList.add(sel);
     }
 
+    @GuardedBy("mLock")
     private void removeLocked(ProgramSelector.Identifier key,
             List<ProgramSelector.Identifier> removedIdentifierList) {
         RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 51236fe3..248b5d0 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -122,10 +122,10 @@
     boolean isFunctionEnabled(String function);
 
     /* Sets the current USB function. */
-    void setCurrentFunctions(long functions);
+    void setCurrentFunctions(long functions, int operationId);
 
     /* Compatibility version of setCurrentFunctions(long). */
-    void setCurrentFunction(String function, boolean usbDataUnlocked);
+    void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId);
 
     /* Gets the current USB functions. */
     long getCurrentFunctions();
diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java
index 19655ed..7fc282c 100644
--- a/core/java/android/hardware/usb/ParcelableUsbPort.java
+++ b/core/java/android/hardware/usb/ParcelableUsbPort.java
@@ -34,11 +34,13 @@
     private final int mSupportedContaminantProtectionModes;
     private final boolean mSupportsEnableContaminantPresenceProtection;
     private final boolean mSupportsEnableContaminantPresenceDetection;
+    private final boolean mSupportsComplianceWarnings;
 
     private ParcelableUsbPort(@NonNull String id, int supportedModes,
             int supportedContaminantProtectionModes,
             boolean supportsEnableContaminantPresenceProtection,
-            boolean supportsEnableContaminantPresenceDetection) {
+            boolean supportsEnableContaminantPresenceDetection,
+            boolean supportsComplianceWarnings) {
         mId = id;
         mSupportedModes = supportedModes;
         mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -46,6 +48,8 @@
                 supportsEnableContaminantPresenceProtection;
         mSupportsEnableContaminantPresenceDetection =
                 supportsEnableContaminantPresenceDetection;
+        mSupportsComplianceWarnings =
+                supportsComplianceWarnings;
     }
 
     /**
@@ -59,7 +63,8 @@
         return new ParcelableUsbPort(port.getId(), port.getSupportedModes(),
                 port.getSupportedContaminantProtectionModes(),
                 port.supportsEnableContaminantPresenceProtection(),
-                port.supportsEnableContaminantPresenceDetection());
+                port.supportsEnableContaminantPresenceDetection(),
+                port.supportsComplianceWarnings());
     }
 
     /**
@@ -72,7 +77,8 @@
     public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) {
         return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes,
                 mSupportsEnableContaminantPresenceProtection,
-                mSupportsEnableContaminantPresenceDetection);
+                mSupportsEnableContaminantPresenceDetection,
+                mSupportsComplianceWarnings);
     }
 
     @Override
@@ -87,6 +93,7 @@
         dest.writeInt(mSupportedContaminantProtectionModes);
         dest.writeBoolean(mSupportsEnableContaminantPresenceProtection);
         dest.writeBoolean(mSupportsEnableContaminantPresenceDetection);
+        dest.writeBoolean(mSupportsComplianceWarnings);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableUsbPort> CREATOR =
@@ -98,11 +105,13 @@
                     int supportedContaminantProtectionModes = in.readInt();
                     boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
                     boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+                    boolean supportsComplianceWarnings = in.readBoolean();
 
                     return new ParcelableUsbPort(id, supportedModes,
                             supportedContaminantProtectionModes,
                             supportsEnableContaminantPresenceProtection,
-                            supportsEnableContaminantPresenceDetection);
+                            supportsEnableContaminantPresenceDetection,
+                            supportsComplianceWarnings);
                 }
 
                 @Override
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 50dd0064..7a8117c 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -52,6 +53,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class allows you to access the state of USB and communicate with USB devices.
@@ -95,7 +97,7 @@
      * If the sticky intent has not been found, that indicates USB is disconnected,
      * USB is not configued, MTP function is enabled, and all the other functions are disabled.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String ACTION_USB_STATE =
@@ -113,6 +115,19 @@
     public static final String ACTION_USB_PORT_CHANGED =
             "android.hardware.usb.action.USB_PORT_CHANGED";
 
+     /**
+     * Broadcast Action: A broadcast for USB compliance warning changes.
+     *
+     * This intent is sent when a port partner's
+     * (USB power source/cable/accessory) compliance warnings change state.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED =
+            "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED";
+
    /**
      * Activity intent sent when user attaches a USB device.
      *
@@ -172,7 +187,7 @@
      * <p>For more information about communicating with USB accessory handshake, refer to
      * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
      *
-     * {@hide}
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @SystemApi
@@ -184,7 +199,7 @@
      * Boolean extra indicating whether USB is connected or disconnected.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_CONNECTED = "connected";
@@ -193,7 +208,7 @@
      * Boolean extra indicating whether USB is connected or disconnected as host.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_HOST_CONNECTED = "host_connected";
 
@@ -201,7 +216,7 @@
      * Boolean extra indicating whether USB is configured.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_CONFIGURED = "configured";
@@ -212,7 +227,7 @@
      * has explicitly asked for this data to be unlocked.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final String USB_DATA_UNLOCKED = "unlocked";
@@ -221,7 +236,7 @@
      * A placeholder indicating that no USB function is being specified.
      * Used for compatibility with old init scripts to indicate no functions vs. charging function.
      *
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage
     public static final String USB_FUNCTION_NONE = "none";
@@ -230,7 +245,7 @@
      * Name of the adb USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_ADB = "adb";
 
@@ -238,7 +253,7 @@
      * Name of the RNDIS ethernet USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_FUNCTION_RNDIS = "rndis";
@@ -247,7 +262,7 @@
      * Name of the MTP USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_MTP = "mtp";
 
@@ -255,7 +270,7 @@
      * Name of the PTP USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_PTP = "ptp";
 
@@ -263,7 +278,7 @@
      * Name of the audio source USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
 
@@ -271,7 +286,7 @@
      * Name of the MIDI USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_MIDI = "midi";
 
@@ -279,7 +294,7 @@
      * Name of the Accessory USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_ACCESSORY = "accessory";
 
@@ -287,7 +302,7 @@
      * Name of the NCM USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_FUNCTION_NCM = "ncm";
@@ -295,32 +310,39 @@
     /**
      * Name of Gadget Hal Not Present;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_UNKNOWN = "unknown";
 
     /**
      * Name of the USB Gadget Hal Version v1.0;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_0 = "V1_0";
 
     /**
      * Name of the USB Gadget Hal Version v1.1;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_1 = "V1_1";
 
     /**
      * Name of the USB Gadget Hal Version v1.2;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_2 = "V1_2";
 
     /**
+     * Name of the USB Gadget Hal Version v2.0;
+     *
+     * @hide
+     */
+    public static final String GADGET_HAL_VERSION_2_0 = "V2_0";
+
+    /**
      * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
      * containing the {@link UsbPort} object for the port.
      *
@@ -356,7 +378,7 @@
      * This is obtained with SystemClock.elapsedRealtime()
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_UEVENT_TIME =
@@ -370,7 +392,7 @@
      * between communicating with USB accessory handshake, refer to
      * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_STRING_COUNT =
@@ -380,7 +402,7 @@
      * Boolean extra indicating whether got start accessory or not
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_START =
@@ -392,7 +414,7 @@
      * sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}.
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_HANDSHAKE_END =
@@ -426,7 +448,7 @@
     /**
      * The Value for USB gadget hal is not presented.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_NOT_SUPPORTED = -1;
@@ -434,7 +456,7 @@
     /**
      * Value for Gadget Hal Version v1.0.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_0 = 10;
@@ -442,7 +464,7 @@
     /**
      * Value for Gadget Hal Version v1.1.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_1 = 11;
@@ -450,15 +472,23 @@
     /**
      * Value for Gadget Hal Version v1.2.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_2 = 12;
 
     /**
+     * Value for Gadget Hal Version v2.0.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int GADGET_HAL_V2_0 = 20;
+
+    /**
      * Value for USB_STATE is not configured.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1;
@@ -466,7 +496,7 @@
     /**
      * Value for USB Transfer Rate of Low Speed in Mbps (real value is 1.5Mbps).
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2;
@@ -474,7 +504,7 @@
     /**
      * Value for USB Transfer Rate of Full Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12;
@@ -482,7 +512,7 @@
     /**
      * Value for USB Transfer Rate of High Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480;
@@ -490,7 +520,7 @@
     /**
      * Value for USB Transfer Rate of Super Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024;
@@ -498,7 +528,7 @@
     /**
      * Value for USB Transfer Rate of 10G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024;
@@ -506,7 +536,7 @@
     /**
      * Value for USB Transfer Rate of 20G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024;
@@ -514,7 +544,7 @@
     /**
      * Value for USB Transfer Rate of 40G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
@@ -530,7 +560,7 @@
     /**
      * The Value for USB hal is not presented.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_NOT_SUPPORTED = -1;
@@ -538,7 +568,7 @@
     /**
      * Value for USB Hal Version v1.0.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_0 = 10;
@@ -546,7 +576,7 @@
     /**
      * Value for USB Hal Version v1.1.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_1 = 11;
@@ -554,7 +584,7 @@
     /**
      * Value for USB Hal Version v1.2.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_2 = 12;
@@ -562,7 +592,7 @@
     /**
      * Value for USB Hal Version v1.3.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_3 = 13;
@@ -577,63 +607,63 @@
 
     /**
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_NONE = 0;
 
     /**
      * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_MTP = GadgetFunction.MTP;
 
     /**
      * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_PTP = GadgetFunction.PTP;
 
     /**
      * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
 
     /**
      * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
 
     /**
      * Code for the accessory usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
 
     /**
      * Code for the audio source usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
 
     /**
      * Code for the adb usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_ADB = GadgetFunction.ADB;
 
     /**
      * Code for the ncm source usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_NCM = 1 << 10;
@@ -643,6 +673,11 @@
 
     private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     static {
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
@@ -674,6 +709,7 @@
             GADGET_HAL_V1_0,
             GADGET_HAL_V1_1,
             GADGET_HAL_V1_2,
+            GADGET_HAL_V2_0,
     })
     public @interface UsbGadgetHalVersion {}
 
@@ -692,7 +728,7 @@
     private final IUsbManager mService;
 
     /**
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UsbManager(Context context, IUsbManager service) {
@@ -803,7 +839,7 @@
      * {@link #FUNCTION_PTP} are supported.
      * @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found.
      *
-     * {@hide}
+     * @hide
      */
     public ParcelFileDescriptor getControlFd(long function) {
         try {
@@ -964,7 +1000,7 @@
      * Only system components can call this function.
      * @param device to request permissions for
      *
-     * {@hide}
+     * @hide
      */
     public void grantPermission(UsbDevice device) {
         grantPermission(device, Process.myUid());
@@ -976,7 +1012,7 @@
      * @param device to request permissions for
      * @uid uid to give permission
      *
-     * {@hide}
+     * @hide
      */
     public void grantPermission(UsbDevice device, int uid) {
         try {
@@ -992,7 +1028,7 @@
      * @param device to request permissions for
      * @param packageName of package to grant permissions
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1017,7 +1053,7 @@
      * @param function name of the USB function
      * @return true if the USB function is enabled
      *
-     * {@hide}
+     * @hide
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1049,14 +1085,17 @@
      * @param functions the USB function(s) to set, as a bitwise mask.
      *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public void setCurrentFunctions(@UsbFunctionMode long functions) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
-            mService.setCurrentFunctions(functions);
+            mService.setCurrentFunctions(functions, operationId);
         } catch (RemoteException e) {
+            Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:"
+                        + functions + ", opId:" + operationId, e);
             throw e.rethrowFromSystemServer();
         }
     }
@@ -1068,14 +1107,17 @@
      * @param functions the USB function(s) to set.
      * @param usbDataUnlocked unused
 
-     * {@hide}
+     * @hide
      */
     @Deprecated
     @UnsupportedAppUsage
     public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
-            mService.setCurrentFunction(functions, usbDataUnlocked);
+            mService.setCurrentFunction(functions, usbDataUnlocked, operationId);
         } catch (RemoteException e) {
+            Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:"
+                        + functions + ", opId:" + operationId, e);
             throw e.rethrowFromSystemServer();
         }
     }
@@ -1090,7 +1132,7 @@
      * @return The currently enabled functions, in a bitwise mask.
      * A zero mask indicates that the current function is the charging function.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1116,7 +1158,7 @@
      * @param functions functions to set, in a bitwise mask.
      *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
-     * {@hide}
+     * @hide
      */
     public void setScreenUnlockedFunctions(long functions) {
         try {
@@ -1132,7 +1174,7 @@
      * @return The currently set screen enabled functions.
      * A zero mask indicates that the screen unlocked functions feature is not enabled.
      *
-     * {@hide}
+     * @hide
      */
     public long getScreenUnlockedFunctions() {
         try {
@@ -1154,19 +1196,17 @@
      *
      * @return The value of currently USB Bandwidth.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public int getUsbBandwidthMbps() {
         int usbSpeed;
-
         try {
             usbSpeed = mService.getCurrentUsbSpeed();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-
         return usbSpeedToBandwidth(usbSpeed);
     }
 
@@ -1178,7 +1218,7 @@
      *
      * @return a integer {@code GADGET_HAL_*} represent hal version.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1198,7 +1238,7 @@
      *
      * @return a integer {@code USB_HAL_*} represent hal version.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1494,7 +1534,7 @@
      * @param usbDeviceConnectionHandler The component to handle usb connections,
      * {@code null} to unset.
      *
-     * {@hide}
+     * @hide
      */
     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
         try {
@@ -1513,7 +1553,7 @@
      *
      * @return Whether the mask is settable.
      *
-     * {@hide}
+     * @hide
      */
     public static boolean areSettableFunctions(long functions) {
         return functions == FUNCTION_NONE
@@ -1527,7 +1567,7 @@
      *
      * @return String representation of given mask
      *
-     * {@hide}
+     * @hide
      */
     public static String usbFunctionsToString(long functions) {
         StringJoiner joiner = new StringJoiner(",");
@@ -1563,7 +1603,7 @@
      *
      * @return A mask of all valid functions in the string
      *
-     * {@hide}
+     * @hide
      */
     public static long usbFunctionsFromString(String functions) {
         if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
@@ -1585,7 +1625,7 @@
      *
      * @return a value of USB bandwidth
      *
-     * {@hide}
+     * @hide
      */
     public static int usbSpeedToBandwidth(int speed) {
         switch (speed) {
@@ -1619,12 +1659,14 @@
      *
      * @return String representation of Usb Gadget Hal Version
      *
-     * {@hide}
+     * @hide
      */
     public static @NonNull String usbGadgetHalVersionToString(int version) {
         String halVersion;
 
-        if (version == GADGET_HAL_V1_2) {
+        if (version == GADGET_HAL_V2_0) {
+            halVersion = GADGET_HAL_VERSION_2_0;
+        } else if (version == GADGET_HAL_V1_2) {
             halVersion = GADGET_HAL_VERSION_1_2;
         } else if (version == GADGET_HAL_V1_1) {
             halVersion = GADGET_HAL_VERSION_1_1;
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 7c5a4c6..e0f9cad 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -46,6 +46,10 @@
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_OTHER;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -83,6 +87,7 @@
     private final int mSupportedContaminantProtectionModes;
     private final boolean mSupportsEnableContaminantPresenceProtection;
     private final boolean mSupportsEnableContaminantPresenceDetection;
+    private final boolean mSupportsComplianceWarnings;
 
     private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
     /**
@@ -250,6 +255,18 @@
             int supportedContaminantProtectionModes,
             boolean supportsEnableContaminantPresenceProtection,
             boolean supportsEnableContaminantPresenceDetection) {
+        this(usbManager, id, supportedModes, supportedContaminantProtectionModes,
+                supportsEnableContaminantPresenceProtection,
+                supportsEnableContaminantPresenceDetection,
+                false);
+    }
+
+    /** @hide */
+    public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
+            int supportedContaminantProtectionModes,
+            boolean supportsEnableContaminantPresenceProtection,
+            boolean supportsEnableContaminantPresenceDetection,
+            boolean supportsComplianceWarnings) {
         Objects.requireNonNull(id);
         Preconditions.checkFlagsArgument(supportedModes,
                 MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY);
@@ -262,6 +279,7 @@
                 supportsEnableContaminantPresenceProtection;
         mSupportsEnableContaminantPresenceDetection =
                 supportsEnableContaminantPresenceDetection;
+        mSupportsComplianceWarnings = supportsComplianceWarnings;
     }
 
     /**
@@ -331,6 +349,21 @@
     }
 
     /**
+     * Queries USB Port to see if the port is capable of identifying
+     * non compliant USB power source/cable/accessory.
+     *
+     * @return true when the UsbPort is capable of identifying
+     *             non compliant USB power
+     *             source/cable/accessory.
+     * @return false otherwise.
+     */
+    @CheckResult
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public boolean supportsComplianceWarnings() {
+        return mSupportsComplianceWarnings;
+    }
+
+    /**
      * Sets the desired role combination of the port.
      * <p>
      * The supported role combinations depend on what is connected to the port and may be
@@ -686,6 +719,37 @@
     }
 
     /** @hide */
+    public static String complianceWarningsToString(@NonNull int[] complianceWarnings) {
+        StringBuilder complianceWarningString = new StringBuilder();
+        complianceWarningString.append("[");
+
+        if (complianceWarnings != null) {
+            for (int warning : complianceWarnings) {
+                switch (warning) {
+                    case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
+                        complianceWarningString.append("other, ");
+                        break;
+                    case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+                        complianceWarningString.append("debug accessory, ");
+                        break;
+                    case UsbPortStatus.COMPLIANCE_WARNING_BC_1_2:
+                        complianceWarningString.append("bc12, ");
+                        break;
+                    case UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP:
+                        complianceWarningString.append("missing rp, ");
+                        break;
+                    default:
+                        complianceWarningString.append(String.format("Unknown(%d), ", warning));
+                        break;
+                }
+            }
+        }
+
+        complianceWarningString.append("]");
+        return complianceWarningString.toString().replaceAll(", ]$", "]");
+    }
+
+    /** @hide */
     public static void checkMode(int powerRole) {
         Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE,
                 Constants.PortMode.NUM_MODES - 1, "portMode");
@@ -720,10 +784,12 @@
     @Override
     public String toString() {
         return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
-                + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
-                + "supportsEnableContaminantPresenceProtection="
+                + ", supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
+                + ", supportsEnableContaminantPresenceProtection="
                 + mSupportsEnableContaminantPresenceProtection
-                + "supportsEnableContaminantPresenceDetection="
-                + mSupportsEnableContaminantPresenceDetection;
+                + ", supportsEnableContaminantPresenceDetection="
+                + mSupportsEnableContaminantPresenceDetection
+                + ", supportsComplianceWarnings="
+                + mSupportsComplianceWarnings;
     }
 }
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 3221ec8..ed3e40d 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -16,9 +16,11 @@
 
 package android.hardware.usb;
 
+import android.Manifest;
+import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -46,6 +48,7 @@
     private final boolean mPowerTransferLimited;
     private final @UsbDataStatus int mUsbDataStatus;
     private final @PowerBrickConnectionStatus int mPowerBrickConnectionStatus;
+    private final @NonNull @ComplianceWarning int[] mComplianceWarnings;
 
     /**
      * Power role: This USB port does not have a power role.
@@ -246,6 +249,41 @@
      */
     public static final int POWER_BRICK_STATUS_DISCONNECTED = 2;
 
+    /**
+     * Used to indicate attached sources/cables/accessories/ports
+     * that do not match the other warnings below and do not meet the
+     * requirements of specifications including but not limited to
+     * USB Type-C Cable and Connector, Universal Serial Bus
+     * Power Delivery, and Universal Serial Bus 1.x/2.0/3.x/4.0.
+     * In addition, constants introduced after the target sdk will be
+     * remapped into COMPLIANCE_WARNING_OTHER.
+     */
+    public static final int COMPLIANCE_WARNING_OTHER = 1;
+
+    /**
+     * Used to indicate Type-C port partner
+     * (cable/accessory/source) that identifies itself as debug
+     * accessory source as defined in USB Type-C Cable and
+     * Connector Specification. However, the specification states
+     * that this is meant for debug only and shall not be used for
+     * with commercial products.
+     */
+    public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2;
+
+    /**
+     * Used to indicate USB ports that does not
+     * identify itself as one of the charging port types (SDP/CDP
+     * DCP etc) as defined by Battery Charging v1.2 Specification.
+     */
+    public static final int COMPLIANCE_WARNING_BC_1_2 = 3;
+
+    /**
+     * Used to indicate Type-C sources/cables that are missing pull
+     * up resistors on the CC pins as required by USB Type-C Cable
+     * and Connector Specification.
+     */
+    public static final int COMPLIANCE_WARNING_MISSING_RP = 4;
+
     @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = {
             CONTAMINANT_DETECTION_NOT_SUPPORTED,
             CONTAMINANT_DETECTION_DISABLED,
@@ -275,6 +313,15 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface UsbPortMode{}
 
+    @IntDef(prefix = { "COMPLIANCE_WARNING_" }, value = {
+            COMPLIANCE_WARNING_OTHER,
+            COMPLIANCE_WARNING_DEBUG_ACCESSORY,
+            COMPLIANCE_WARNING_BC_1_2,
+            COMPLIANCE_WARNING_MISSING_RP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ComplianceWarning{}
+
     /** @hide */
     @IntDef(prefix = { "DATA_STATUS_" }, flag = true, value = {
             DATA_STATUS_UNKNOWN,
@@ -302,7 +349,8 @@
             int supportedRoleCombinations, int contaminantProtectionStatus,
             int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus,
             boolean powerTransferLimited,
-            @PowerBrickConnectionStatus int powerBrickConnectionStatus) {
+            @PowerBrickConnectionStatus int powerBrickConnectionStatus,
+            @NonNull @ComplianceWarning int[] complianceWarnings) {
         mCurrentMode = currentMode;
         mCurrentPowerRole = currentPowerRole;
         mCurrentDataRole = currentDataRole;
@@ -312,21 +360,29 @@
         mUsbDataStatus = usbDataStatus;
         mPowerTransferLimited = powerTransferLimited;
         mPowerBrickConnectionStatus = powerBrickConnectionStatus;
+        mComplianceWarnings = complianceWarnings;
+    }
+
+    /** @hide */
+    public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
+            int supportedRoleCombinations, int contaminantProtectionStatus,
+            int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus,
+            boolean powerTransferLimited,
+            @PowerBrickConnectionStatus int powerBrickConnectionStatus) {
+        this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations,
+                contaminantProtectionStatus, contaminantDetectionStatus,
+                usbDataStatus, powerTransferLimited, powerBrickConnectionStatus,
+                new int[] {});
     }
 
     /** @hide */
     public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
             int supportedRoleCombinations, int contaminantProtectionStatus,
             int contaminantDetectionStatus) {
-        mCurrentMode = currentMode;
-        mCurrentPowerRole = currentPowerRole;
-        mCurrentDataRole = currentDataRole;
-        mSupportedRoleCombinations = supportedRoleCombinations;
-        mContaminantProtectionStatus = contaminantProtectionStatus;
-        mContaminantDetectionStatus = contaminantDetectionStatus;
-        mUsbDataStatus = DATA_STATUS_UNKNOWN;
-        mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN;
-        mPowerTransferLimited = false;
+        this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations,
+                contaminantProtectionStatus, contaminantDetectionStatus,
+                DATA_STATUS_UNKNOWN, false, POWER_BRICK_STATUS_UNKNOWN,
+                new int[] {});
     }
 
     /**
@@ -443,6 +499,21 @@
         return mPowerBrickConnectionStatus;
     }
 
+    /**
+     * Returns non compliant reasons, if any, for the connected
+     * charger/cable/accessory/USB port.
+     *
+     * @return array including {@link #NON_COMPLIANT_REASON_DEBUG_ACCESSORY},
+     *         {@link #NON_COMPLIANT_REASON_BC12},
+     *         {@link #NON_COMPLIANT_REASON_MISSING_RP},
+     *         or {@link #NON_COMPLIANT_REASON_TYPEC}
+     */
+    @CheckResult
+    @NonNull
+    public @ComplianceWarning int[] getComplianceWarnings() {
+        return mComplianceWarnings;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -460,9 +531,11 @@
                         + UsbPort.usbDataStatusToString(getUsbDataStatus())
                 + ", isPowerTransferLimited="
                         + isPowerTransferLimited()
-                +", powerBrickConnectionStatus="
+                + ", powerBrickConnectionStatus="
                         + UsbPort
                             .powerBrickConnectionStatusToString(getPowerBrickConnectionStatus())
+                + ", complianceWarnings="
+                        + UsbPort.complianceWarningsToString(getComplianceWarnings())
                 + "}";
     }
 
@@ -482,6 +555,7 @@
         dest.writeInt(mUsbDataStatus);
         dest.writeBoolean(mPowerTransferLimited);
         dest.writeInt(mPowerBrickConnectionStatus);
+        dest.writeIntArray(mComplianceWarnings);
     }
 
     public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -497,10 +571,12 @@
             int usbDataStatus = in.readInt();
             boolean powerTransferLimited = in.readBoolean();
             int powerBrickConnectionStatus = in.readInt();
+            @ComplianceWarning int[] complianceWarnings = in.createIntArray();
             return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
                     contaminantDetectionStatus, usbDataStatus, powerTransferLimited,
-                    powerBrickConnectionStatus);
+                    powerBrickConnectionStatus,
+                    complianceWarnings);
         }
 
         @Override
@@ -524,6 +600,7 @@
         private boolean mPowerTransferLimited;
         private @UsbDataStatus int mUsbDataStatus;
         private @PowerBrickConnectionStatus int mPowerBrickConnectionStatus;
+        private @ComplianceWarning int[] mComplianceWarnings;
 
         public Builder() {
             mCurrentMode = MODE_NONE;
@@ -533,6 +610,7 @@
             mContaminantDetectionStatus = CONTAMINANT_DETECTION_NOT_SUPPORTED;
             mUsbDataStatus = DATA_STATUS_UNKNOWN;
             mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN;
+            mComplianceWarnings = new int[] {};
         }
 
         /**
@@ -619,6 +697,20 @@
         }
 
         /**
+         * Sets the non-compliant charger reasons of {@link UsbPortStatus}
+         *
+         * @return Instance of {@link Builder}
+         */
+        @NonNull
+        public Builder setComplianceWarnings(
+                @NonNull int[] complianceWarnings) {
+            mComplianceWarnings = complianceWarnings == null ? new int[] {} :
+                    complianceWarnings;
+            return this;
+        }
+
+
+        /**
          * Creates the {@link UsbPortStatus} object.
          */
         @NonNull
@@ -626,7 +718,7 @@
             UsbPortStatus status = new UsbPortStatus(mCurrentMode, mCurrentPowerRole,
                     mCurrentDataRole, mSupportedRoleCombinations, mContaminantProtectionStatus,
                     mContaminantDetectionStatus, mUsbDataStatus, mPowerTransferLimited,
-                    mPowerBrickConnectionStatus);
+                    mPowerBrickConnectionStatus, mComplianceWarnings);
             return status;
         }
     };
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index fb66cb9..872414a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -70,11 +70,14 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -98,6 +101,7 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
 import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -158,6 +162,8 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.RingBuffer;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -730,7 +736,6 @@
         @Override
         public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
-            mConfigTracker.onInitialize(params.configChanges);
             mPrivOps.set(params.privilegedOperations);
             InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
             mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
@@ -1601,6 +1606,8 @@
         mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_hideNavBarForKeyboard);
 
+        initConfigurationTracker();
+
         // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
         // for update resources & configuration correctly when show soft input
         // in non-default display.
@@ -1656,6 +1663,36 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    private void initConfigurationTracker() {
+        final int flags = PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        final ComponentName imeComponent = new ComponentName(
+                getPackageName(), getClass().getName());
+        final String imeId = imeComponent.flattenToShortString();
+        final ServiceInfo si;
+        try {
+            si = getPackageManager().getServiceInfo(imeComponent,
+                    PackageManager.ComponentInfoFlags.of(flags));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.wtf(TAG, "Unable to find input method " + imeId, e);
+            return;
+        }
+        try (XmlResourceParser parser = si.loadXmlMetaData(getPackageManager(),
+                InputMethod.SERVICE_META_DATA);
+             TypedArray sa = getResources().obtainAttributes(Xml.asAttributeSet(parser),
+                     com.android.internal.R.styleable.InputMethod)) {
+            if (parser == null) {
+                throw new XmlPullParserException(
+                        "No " + InputMethod.SERVICE_META_DATA + " meta-data");
+            }
+            final int handledConfigChanges = sa.getInt(
+                    com.android.internal.R.styleable.InputMethod_configChanges, 0);
+            mConfigTracker.onInitialize(handledConfigChanges);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Unable to load input method " + imeId, e);
+        }
+    }
+
     /**
      * This is a hook that subclasses can use to perform initialization of
      * their interface.  It is called for you prior to any of your UI objects
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index a887f2a..eae7ce0 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -58,6 +58,7 @@
     void setUserIcon(int userId, in Bitmap icon);
     ParcelFileDescriptor getUserIcon(int userId);
     UserInfo getPrimaryUser();
+    int getMainUserId();
     List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
     List<UserInfo> getProfiles(int userId, boolean enabledOnly);
     int[] getProfileIds(int userId, boolean enabledOnly);
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9ea4278..394927e 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -252,10 +252,12 @@
     }
 
     /**
-     * Returns the list of declared instances for an interface.
+     * Returns an array of all declared instances for a particular interface.
      *
-     * @return true if the service is declared somewhere (eg. VINTF manifest) and
-     * waitForService should always be able to return the service.
+     * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+     * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+     * returned.
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1f21bfe..954d1fc 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2377,14 +2377,16 @@
     }
 
     /**
-     * Returns true if the context user is the designated "main user" of the device. This user may
-     * have access to certain features which are limited to at most one user.
+     * Returns {@code true} if the context user is the designated "main user" of the device. This
+     * user may have access to certain features which are limited to at most one user. There will
+     * never be more than one main user on a device.
      *
-     * <p>Currently, the first human user on the device will be the main user; in the future, the
-     * concept may be transferable, so a different user (or even no user at all) may be designated
-     * the main user instead.
+     * <p>Currently, on most form factors the first human user on the device will be the main user;
+     * in the future, the concept may be transferable, so a different user (or even no user at all)
+     * may be designated the main user instead. On other form factors there might not be a main
+     * user.
      *
-     * <p>Note that this will be the not be the system user on devices for which
+     * <p>Note that this will not be the system user on devices for which
      * {@link #isHeadlessSystemUserMode()} returns true.
      * @hide
      */
@@ -2400,6 +2402,29 @@
     }
 
     /**
+     * Returns the designated "main user" of the device, or {@code null} if there is no main user.
+     *
+     * @see #isMainUser()
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    public @Nullable UserHandle getMainUser() {
+        try {
+            final int mainUserId = mService.getMainUserId();
+            if (mainUserId == UserHandle.USER_NULL) {
+                return null;
+            }
+            return UserHandle.of(mainUserId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Used to check if the context user is an admin user. An admin user is allowed to
      * modify or configure certain settings that aren't available to non-admin users,
      * create and delete additional users, etc. There can be more than one admin users.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 71bc4b3..3448a9e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -227,6 +227,31 @@
     }
 
     /**
+     * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
+     * vibration components) that is equivalent to this VibrationEffect.
+     *
+     * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
+     * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
+     * created with other means becomes converted into an equivalent legacy vibration pattern, even
+     * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
+     * vibration pattern for such effects, it will return {@code null}.
+     *
+     * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
+     * form of repeating behavior, regardless of how the effect was created. For repeating effects,
+     * the method will always return {@code null}.
+     *
+     * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
+     *               the method successfully derived a vibration pattern equivalent to the effect
+     *               (this will always be the case if the effect was created via
+     *               {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+     *               {@code null}.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
+
+    /**
      * Create a waveform vibration.
      *
      * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
@@ -641,6 +666,51 @@
             return mRepeatIndex;
         }
 
+         /** @hide */
+        @Override
+        @Nullable
+        public long[] computeCreateWaveformOffOnTimingsOrNull() {
+            if (getRepeatIndex() >= 0) {
+                // Repeating effects cannot be fully represented as a long[] legacy pattern.
+                return null;
+            }
+
+            List<VibrationEffectSegment> segments = getSegments();
+
+            // The maximum possible size of the final pattern is 1 plus the number of segments in
+            // the original effect. This is because we will add an empty "off" segment at the
+            // start of the pattern if the first segment of the original effect is an "on" segment.
+            // (because the legacy patterns start with an "off" pattern). Other than this one case,
+            // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
+            // that are all "on" or "off") and create a pattern entry for the total duration, which
+            // will not take more number pattern entries than the number of segments processed.
+            long[] patternBuffer = new long[segments.size() + 1];
+            int patternIndex = 0;
+
+            for (int i = 0; i < segments.size(); i++) {
+                StepSegment stepSegment =
+                        castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
+                if (stepSegment == null) {
+                    // This means that there is 1 or more segments of this effect that is/are not a
+                    // possible component of a legacy vibration pattern. Thus, the VibrationEffect
+                    // does not have any equivalent legacy vibration pattern.
+                    return null;
+                }
+
+                boolean isSegmentOff = stepSegment.getAmplitude() == 0;
+                // Even pattern indices are "off", and odd pattern indices are "on"
+                boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
+                if (isSegmentOff != isCurrentPatternIndexOff) {
+                    // Move the pattern index one step ahead, so that the current segment's
+                    // "off"/"on" property matches that of the index's
+                    ++patternIndex;
+                }
+                patternBuffer[patternIndex] += stepSegment.getDuration();
+            }
+
+            return Arrays.copyOf(patternBuffer, patternIndex + 1);
+        }
+
         /** @hide */
         @Override
         public void validate() {
@@ -806,6 +876,31 @@
                         return new Composed[size];
                     }
                 };
+
+        /**
+         * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
+         * only if it can possibly be a segment for an effect created via
+         * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+         */
+        @Nullable
+        private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
+                VibrationEffectSegment segment) {
+            if (!(segment instanceof StepSegment)) {
+                return null;
+            }
+
+            StepSegment stepSegment = (StepSegment) segment;
+            if (stepSegment.getFrequencyHz() != 0) {
+                return null;
+            }
+
+            float amplitude = stepSegment.getAmplitude();
+            if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
+                return null;
+            }
+
+            return stepSegment;
+        }
     }
 
     /**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 19e7bd4..ca88ae3 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -262,6 +262,14 @@
     public static final String NAMESPACE_GAME_DRIVER = "game_driver";
 
     /**
+     * Namespace for all HDMI Control features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
+
+    /**
      * Namespace for all input-related features that are used at the native level.
      * These features are applied at reboot.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 630ad6c..2adbbcd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -708,7 +708,7 @@
             "android.settings.WIFI_SETTINGS";
 
     /**
-     * Activity Action: Show settings to allow configuration of MTE.
+     * Activity Action: Show settings to allow configuration of Advanced memory protection.
      * <p>
      * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
      * classes of security problems at a small runtime performance cost overhead.
@@ -720,8 +720,8 @@
      * Output: Nothing.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_MEMTAG_SETTINGS =
-            "android.settings.MEMTAG_SETTINGS";
+    public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS =
+            "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS";
 
     /**
      * Activity Action: Show settings to allow configuration of a static IP
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 553a324..7757081 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -27,8 +27,6 @@
 /**
  * An action defined by the provider that intents into the provider's app for specific
  * user actions.
- *
- * @hide
  */
 public final class Action implements Parcelable {
     /** Slice object containing display content to be displayed with this action on the UI. */
@@ -39,6 +37,13 @@
     /**
      * Constructs an action to be displayed on the UI.
      *
+     * <p> Actions must be used for any provider related operations, such as opening the provider
+     * app, intenting straight into certain app activities like 'manage credentials', top
+     * level authentication before displaying any content etc.
+     *
+     * <p> See details on usage of {@code Action} for various actionable entries in
+     * {@link BeginCreateCredentialResponse} and {@link GetCredentialsResponse}.
+     *
      * @param slice the display content to be displayed on the UI, along with this action
      * @param pendingIntent the intent to be invoked when the user selects this action
      */
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
new file mode 100644
index 0000000..30cab8d
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable BeginCreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.java b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
new file mode 100644
index 0000000..1918d8c
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.service.credentials;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for beginning a create credential request.
+ *
+ * See {@link BeginCreateCredentialResponse} for the counterpart response
+ */
+public final class BeginCreateCredentialRequest implements Parcelable {
+    private final @NonNull String mCallingPackage;
+    private final @NonNull String mType;
+    private final @NonNull Bundle mData;
+
+    /**
+     * Constructs a new instance.
+     *
+     * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+     * null or empty.
+     * @throws NullPointerException If {@code data} is null.
+     */
+    public BeginCreateCredentialRequest(@NonNull String callingPackage,
+            @NonNull String type, @NonNull Bundle data) {
+        mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+                "callingPackage must not be null or empty");
+        mType = Preconditions.checkStringNotEmpty(type,
+                "type must not be null or empty");
+        mData = Objects.requireNonNull(data, "data must not be null");
+    }
+
+    private BeginCreateCredentialRequest(@NonNull Parcel in) {
+        mCallingPackage = in.readString8();
+        mType = in.readString8();
+        mData = in.readBundle(Bundle.class.getClassLoader());
+    }
+
+    public static final @NonNull Creator<BeginCreateCredentialRequest> CREATOR =
+            new Creator<BeginCreateCredentialRequest>() {
+                @Override
+                public BeginCreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+                    return new BeginCreateCredentialRequest(in);
+                }
+
+                @Override
+                public BeginCreateCredentialRequest[] newArray(int size) {
+                    return new BeginCreateCredentialRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mCallingPackage);
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+    }
+
+    /** Returns the calling package of the calling app. */
+    @NonNull
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /** Returns the type of the credential to be created. */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /** Returns the data to be used while resolving the credential to create. */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
similarity index 93%
rename from core/java/android/service/credentials/CreateCredentialResponse.aidl
rename to core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
index 73c9147..d2a1408 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
@@ -16,4 +16,4 @@
 
 package android.service.credentials;
 
-parcelable CreateCredentialResponse;
+parcelable BeginCreateCredentialResponse;
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
new file mode 100644
index 0000000..022678e
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -0,0 +1,150 @@
+/*
+ * 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.service.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response to a {@link BeginCreateCredentialRequest}.
+ */
+public final class BeginCreateCredentialResponse implements Parcelable {
+    private final @NonNull List<CreateEntry> mCreateEntries;
+    private final @Nullable CreateEntry mRemoteCreateEntry;
+
+    private BeginCreateCredentialResponse(@NonNull Parcel in) {
+        List<CreateEntry> createEntries = new ArrayList<>();
+        in.readTypedList(createEntries, CreateEntry.CREATOR);
+        mCreateEntries = createEntries;
+        mRemoteCreateEntry = in.readTypedObject(CreateEntry.CREATOR);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedList(mCreateEntries);
+        dest.writeTypedObject(mRemoteCreateEntry, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<BeginCreateCredentialResponse> CREATOR =
+            new Creator<BeginCreateCredentialResponse>() {
+                @Override
+                public BeginCreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+                    return new BeginCreateCredentialResponse(in);
+                }
+
+                @Override
+                public BeginCreateCredentialResponse[] newArray(int size) {
+                    return new BeginCreateCredentialResponse[size];
+                }
+            };
+
+    /* package-private */ BeginCreateCredentialResponse(
+            @NonNull List<CreateEntry> createEntries,
+            @Nullable CreateEntry remoteCreateEntry) {
+        this.mCreateEntries = createEntries;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mCreateEntries);
+        this.mRemoteCreateEntry = remoteCreateEntry;
+    }
+
+    /** Returns the list of create entries to be displayed on the UI. */
+    public @NonNull List<CreateEntry> getCreateEntries() {
+        return mCreateEntries;
+    }
+
+    /** Returns the remote create entry to be displayed on the UI. */
+    public @Nullable CreateEntry getRemoteCreateEntry() {
+        return mRemoteCreateEntry;
+    }
+
+    /**
+     * A builder for {@link BeginCreateCredentialResponse}
+     */
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    public static final class Builder {
+        private @NonNull List<CreateEntry> mCreateEntries = new ArrayList<>();
+        private @Nullable CreateEntry mRemoteCreateEntry;
+
+        /**
+         * Sets the list of create entries to be shown on the UI.
+         *
+         * @throws IllegalArgumentException If {@code createEntries} is empty.
+         * @throws NullPointerException If {@code createEntries} is null, or any of its elements
+         * are null.
+         */
+        public @NonNull Builder setCreateEntries(@NonNull List<CreateEntry> createEntries) {
+            Preconditions.checkCollectionNotEmpty(createEntries, "createEntries");
+            mCreateEntries = Preconditions.checkCollectionElementsNotNull(
+                    createEntries, "createEntries");
+            return this;
+        }
+
+        /**
+         * Adds an entry to the list of create entries to be shown on the UI.
+         *
+         * @throws NullPointerException If {@code createEntry} is null.
+         */
+        public @NonNull Builder addCreateEntry(@NonNull CreateEntry createEntry) {
+            mCreateEntries.add(Objects.requireNonNull(createEntry));
+            return this;
+        }
+
+        /**
+         * Sets a remote create entry to be shown on the UI. Provider must set this entry if they
+         * wish to create the credential on a different device.
+         *
+         * <p> When constructing the {@link CreateEntry} object, the {@code pendingIntent} must be
+         * set such that it leads to an activity that can provide UI to fulfill the request on
+         * a remote device. When user selects this {@code remoteCreateEntry}, the system will
+         * invoke the {@code pendingIntent} set on the {@link CreateEntry}.
+         *
+         * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+         * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+         * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESULT} key should be populated
+         * with a {@link android.credentials.CreateCredentialResponse} object.
+         */
+        public @NonNull Builder setRemoteCreateEntry(@Nullable CreateEntry remoteCreateEntry) {
+            mRemoteCreateEntry = remoteCreateEntry;
+            return this;
+        }
+
+        /**
+         * Builds a new instance of {@link BeginCreateCredentialResponse}.
+         *
+         * @throws NullPointerException If {@code createEntries} is null.
+         * @throws IllegalArgumentException If {@code createEntries} is empty.
+         */
+        public @NonNull BeginCreateCredentialResponse build() {
+            Preconditions.checkCollectionNotEmpty(mCreateEntries, "createEntries must "
+                    + "not be null, or empty");
+            return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
deleted file mode 100644
index eb7fba9..0000000
--- a/core/java/android/service/credentials/CreateCredentialRequest.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.service.credentials;
-
-parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
index e6da349..aee85ab 100644
--- a/core/java/android/service/credentials/CreateCredentialRequest.java
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -27,8 +27,6 @@
 
 /**
  * Request for creating a credential.
- *
- * @hide
  */
 public final class CreateCredentialRequest implements Parcelable {
     private final @NonNull String mCallingPackage;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
deleted file mode 100644
index f69dca8..0000000
--- a/core/java/android/service/credentials/CreateCredentialResponse.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 android.service.credentials;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Response to a {@link CreateCredentialRequest}.
- *
- * @hide
- */
-public final class CreateCredentialResponse implements Parcelable {
-    private final @NonNull List<SaveEntry> mSaveEntries;
-    private final @Nullable Action mRemoteSaveEntry;
-    //TODO : Add actions if needed
-
-    private CreateCredentialResponse(@NonNull Parcel in) {
-        List<SaveEntry> saveEntries = new ArrayList<>();
-        in.readTypedList(saveEntries, SaveEntry.CREATOR);
-        mSaveEntries = saveEntries;
-        mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedList(mSaveEntries);
-        dest.writeTypedObject(mRemoteSaveEntry, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
-            new Creator<CreateCredentialResponse>() {
-                @Override
-                public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
-                    return new CreateCredentialResponse(in);
-                }
-
-                @Override
-                public CreateCredentialResponse[] newArray(int size) {
-                    return new CreateCredentialResponse[size];
-                }
-            };
-
-    /* package-private */ CreateCredentialResponse(
-            @NonNull List<SaveEntry> saveEntries,
-            @Nullable Action remoteSaveEntry) {
-        this.mSaveEntries = saveEntries;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSaveEntries);
-        this.mRemoteSaveEntry = remoteSaveEntry;
-    }
-
-    /** Returns the list of save entries to be displayed on the UI. */
-    public @NonNull List<SaveEntry> getSaveEntries() {
-        return mSaveEntries;
-    }
-
-    /** Returns the remote save entry to be displayed on the UI. */
-    public @NonNull Action getRemoteSaveEntry() {
-        return mRemoteSaveEntry;
-    }
-
-    /**
-     * A builder for {@link CreateCredentialResponse}
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static final class Builder {
-        private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
-        private @Nullable Action mRemoteSaveEntry;
-
-        /**
-         * Sets the list of save entries to be shown on the UI.
-         *
-         * @throws IllegalArgumentException If {@code saveEntries} is empty.
-         * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
-         * are null.
-         */
-        public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
-            Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
-            mSaveEntries = Preconditions.checkCollectionElementsNotNull(
-                    saveEntries, "saveEntries");
-            return this;
-        }
-
-        /**
-         * Adds an entry to the list of save entries to be shown on the UI.
-         *
-         * @throws NullPointerException If {@code saveEntry} is null.
-         */
-        public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
-            mSaveEntries.add(Objects.requireNonNull(saveEntry));
-            return this;
-        }
-
-        /**
-         * Sets a remote save entry to be shown on the UI.
-         */
-        public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
-            mRemoteSaveEntry = remoteSaveEntry;
-            return this;
-        }
-
-        /**
-         * Builds the instance.
-         *
-         * @throws IllegalArgumentException If {@code saveEntries} is empty.
-         */
-        public @NonNull CreateCredentialResponse build() {
-            Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
-                    + "not be empty");
-            return new CreateCredentialResponse(
-                    mSaveEntries,
-                    mRemoteSaveEntry);
-        }
-    }
-}
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/CreateEntry.java
similarity index 77%
rename from core/java/android/service/credentials/SaveEntry.java
rename to core/java/android/service/credentials/CreateEntry.java
index 55ff6ff..eb25e25 100644
--- a/core/java/android/service/credentials/SaveEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -25,27 +25,25 @@
 /**
  * An entry to be shown on the UI. This entry represents where the credential to be created will
  * be stored. Examples include user's account, family group etc.
- *
- * @hide
  */
-public final class SaveEntry implements Parcelable {
+public final class CreateEntry implements Parcelable {
     private final @NonNull Slice mSlice;
     private final @NonNull PendingIntent mPendingIntent;
 
-    private SaveEntry(@NonNull Parcel in) {
+    private CreateEntry(@NonNull Parcel in) {
         mSlice = in.readTypedObject(Slice.CREATOR);
         mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
     }
 
-    public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
+    public static final @NonNull Creator<CreateEntry> CREATOR = new Creator<CreateEntry>() {
         @Override
-        public SaveEntry createFromParcel(@NonNull Parcel in) {
-            return new SaveEntry(in);
+        public CreateEntry createFromParcel(@NonNull Parcel in) {
+            return new CreateEntry(in);
         }
 
         @Override
-        public SaveEntry[] newArray(int size) {
-            return new SaveEntry[size];
+        public CreateEntry[] newArray(int size) {
+            return new CreateEntry[size];
         }
     };
 
@@ -61,12 +59,12 @@
     }
 
     /**
-     * Constructs a save entry to be displayed on the UI.
+     * Constructs a CreateEntry to be displayed on the UI.
      *
      * @param slice the display content to be displayed on the UI, along with this entry
      * @param pendingIntent the intent to be invoked when the user selects this entry
      */
-    public SaveEntry(
+    public CreateEntry(
             @NonNull Slice slice,
             @NonNull PendingIntent pendingIntent) {
         this.mSlice = slice;
@@ -77,12 +75,12 @@
                 NonNull.class, null, mPendingIntent);
     }
 
-    /** Returns the content to be displayed with this save entry on the UI. */
+    /** Returns the content to be displayed with this create entry on the UI. */
     public @NonNull Slice getSlice() {
         return mSlice;
     }
 
-    /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
+    /** Returns the pendingIntent to be invoked when this create entry on the UI is selectcd. */
     public @NonNull PendingIntent getPendingIntent() {
         return mPendingIntent;
     }
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 98c537a..941db02b 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -31,8 +31,6 @@
 /**
  * A credential entry that is displayed on the account selector UI. Each entry corresponds to
  * something that the user can select.
- *
- * @hide
  */
 public final class CredentialEntry implements Parcelable {
     /** The type of the credential entry to be shown on the UI. */
@@ -145,61 +143,67 @@
         private boolean mAutoSelectAllowed = false;
 
         /**
-         * Builds the instance.
+         * Creates a builder for a {@link CredentialEntry} that should invoke a
+         * {@link PendingIntent} when selected by the user.
+         *
+         * <p>The {@code pendingIntent} can be used to launch activities that require some user
+         * engagement before getting the credential corresponding to this entry,
+         * e.g. authentication, confirmation etc.
+         * Once the activity fulfills the required user engagement, the
+         * {@link android.app.Activity} result should be set to
+         * {@link android.app.Activity#RESULT_OK}, and the
+         * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} must be set with a
+         * {@link Credential} object.
+         *
          * @param type the type of credential underlying this credential entry
          * @param slice the content to be displayed with this entry on the UI
+         * @param pendingIntent the pendingIntent to be invoked when this entry is selected by the
+         *                      user
          *
-         * @throws IllegalArgumentException If {@code type} is null or empty.
-         * @throws NullPointerException If {@code slice} is null.
+         * @throws NullPointerException If {@code slice}, or {@code pendingIntent} is null.
+         * @throws IllegalArgumentException If {@code type} is null or empty, or if
+         * {@code pendingIntent} was not created with {@link PendingIntent#getActivity}
+         * or {@link PendingIntent#getActivities}.
          */
-        public Builder(@NonNull String type, @NonNull Slice slice) {
+        public Builder(@NonNull String type, @NonNull Slice slice,
+                @NonNull PendingIntent pendingIntent) {
             mType = Preconditions.checkStringNotEmpty(type, "type must not be "
                     + "null, or empty");
             mSlice = Objects.requireNonNull(slice,
                     "slice must not be null");
+            mPendingIntent = Objects.requireNonNull(pendingIntent,
+                    "pendingIntent must not be null");
+            if (!mPendingIntent.isActivity()) {
+                throw new IllegalStateException("Pending intent must start an activity");
+            }
         }
 
         /**
-         * Sets the pendingIntent to be invoked if the user selects this entry.
+         * Creates a builder for a {@link CredentialEntry} that contains a {@link Credential},
+         * and does not require further action.
+         * @param type the type of credential underlying this credential entry
+         * @param slice the content to be displayed with this entry on the UI
+         * @param credential the credential to be returned to the client app, when this entry is
+         *                   selected by the user
          *
-         * The pending intent can be used to launch activities that require some user engagement
-         * before getting the credential corresponding to this entry, e.g. authentication,
-         * confirmation etc.
-         * Once the activity fulfills the required user engagement, a {@link Credential} object
-         * must be returned as an extra on activity finish.
-         *
-         * @throws IllegalStateException If {@code credential} is already set. Must either set the
-         * {@code credential}, or the {@code pendingIntent}.
+         * @throws IllegalArgumentException If {@code type} is null or empty.
+         * @throws NullPointerException If {@code slice}, or {@code credential} is null.
          */
-        public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
-            if (pendingIntent != null) {
-                Preconditions.checkState(mCredential == null,
-                        "credential is already set. Cannot set both the pendingIntent "
-                                + "and the credential");
-            }
-            mPendingIntent = pendingIntent;
-            return this;
-        }
-
-        /**
-         * Sets the credential to be used, if the user selects this entry.
-         *
-         * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
-         * the {@code pendingIntent}, or the {@code credential}.
-         */
-        public @NonNull Builder setCredential(@Nullable Credential credential) {
-            if (credential != null) {
-                Preconditions.checkState(mPendingIntent == null,
-                        "pendingIntent is already set. Cannot set both the "
-                                + "pendingIntent and the credential");
-            }
-            mCredential = credential;
-            return this;
+        public Builder(@NonNull String type, @NonNull Slice slice, @NonNull Credential credential) {
+            mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+                    + "null, or empty");
+            mSlice = Objects.requireNonNull(slice,
+                    "slice must not be null");
+            mCredential = Objects.requireNonNull(credential,
+                    "credential must not be null");
         }
 
         /**
          * Sets whether the entry is allowed to be auto selected by the framework.
          * The default value is set to false.
+         *
+         * <p> The entry is only auto selected if it is the only entry on the user selector,
+         * AND the developer has also enabled auto select, while building the request.
          */
         public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
             mAutoSelectAllowed = autoSelectAllowed;
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index 06f0052..02b7443 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -24,8 +24,6 @@
 
 /**
  * Contains custom exceptions to be used by credential providers on failure.
- *
- * @hide
  */
 public class CredentialProviderException extends Exception {
     public static final int ERROR_UNKNOWN = 0;
@@ -59,6 +57,11 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CredentialProviderError { }
 
+    public CredentialProviderException(@CredentialProviderError int errorCode,
+            @NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+        mErrorCode = errorCode;
+    }
 
     public CredentialProviderException(@CredentialProviderError int errorCode,
             @NonNull String message) {
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 24b7c3c..32646e6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -37,30 +37,63 @@
 /**
  * Service to be extended by credential providers, in order to return user credentials
  * to the framework.
- *
- * @hide
  */
 public abstract class CredentialProviderService extends Service {
-    /** Extra to be used by provider to populate the credential when ending the activity started
-     * through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
-    public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
-            "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
-
-    /** Extra to be used by provider to populate the {@link CredentialsDisplayContent} when
-     * an authentication action entry is selected. **/
-    public static final String EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT =
-            "android.service.credentials.extra.GET_CREDENTIALS_DISPLAY_CONTENT";
+    /**
+     * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with
+     * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry}
+     * returned as part of the {@link BeginCreateCredentialResponse}
+     *
+     * <p>
+     * Type: {@link android.credentials.CreateCredentialRequest}
+     */
+    public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
 
     /**
-     * Provider must read the value against this extra to receive the complete create credential
-     * request parameters, when a pending intent is launched.
+     * Intent extra: The result of a create flow operation, to be set on finish of the
+     * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+     * a {@link CreateEntry}.
+     *
+     * <p>
+     * Type: {@link android.credentials.CreateCredentialResponse}
      */
-    public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
-            "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+    public static final String EXTRA_CREATE_CREDENTIAL_RESULT =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
 
-    /** Extra to be used by the provider when setting the credential result. */
-    public static final String EXTRA_GET_CREDENTIAL =
-            "android.service.credentials.extra.GET_CREDENTIAL";
+    /**
+     * Intent extra: The result of a get credential flow operation, to be set on finish of the
+     * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+     * a {@link CredentialEntry}.
+     *
+     * <p>
+     * Type: {@link android.credentials.Credential}
+     */
+    public static final String EXTRA_CREDENTIAL_RESULT =
+            "android.service.credentials.extra.CREDENTIAL_RESULT";
+
+    /**
+     * Intent extra: The result of an authentication flow, to be set on finish of the
+     * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
+     * a {@link GetCredentialsResponse}. This result should contain the actual content, including
+     * credential entries and action entries, to be shown on the selector.
+     *
+     * <p>
+     * Type: {@link CredentialsResponseContent}
+     */
+    public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT =
+            "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+
+    /**
+     * Intent extra: The error result of any {@link android.app.PendingIntent} flow, to be set
+     * on finish of the corresponding {@link android.app.Activity}. This result should contain an
+     * error code, representing the error encountered by the provider.
+     *
+     * <p>
+     * Type: {@link String}
+     */
+    public static final String EXTRA_ERROR =
+            "android.service.credentials.extra.ERROR";
 
     private static final String TAG = "CredProviderService";
 
@@ -129,20 +162,21 @@
         }
 
         @Override
-        public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
-                ICreateCredentialCallback callback) {
+        public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
+                IBeginCreateCredentialCallback callback) {
             Objects.requireNonNull(request);
             Objects.requireNonNull(callback);
 
             ICancellationSignal transport = CancellationSignal.createTransport();
 
             mHandler.sendMessage(obtainMessage(
-                    CredentialProviderService::onCreateCredential,
+                    CredentialProviderService::onBeginCreateCredential,
                     CredentialProviderService.this, request,
                     CancellationSignal.fromTransport(transport),
-                    new OutcomeReceiver<CreateCredentialResponse, CredentialProviderException>() {
+                    new OutcomeReceiver<
+                            BeginCreateCredentialResponse, CredentialProviderException>() {
                         @Override
-                        public void onResult(CreateCredentialResponse result) {
+                        public void onResult(BeginCreateCredentialResponse result) {
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
@@ -182,8 +216,8 @@
      *                           the android system.
      * @param callback Object used to relay the response of the credential creation request.
      */
-    public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+    public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<CreateCredentialResponse,
+            @NonNull OutcomeReceiver<BeginCreateCredentialResponse,
                     CredentialProviderException> callback);
 }
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsResponseContent.java
similarity index 64%
rename from core/java/android/service/credentials/CredentialsDisplayContent.java
rename to core/java/android/service/credentials/CredentialsResponseContent.java
index 4b23800..32cab50 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsResponseContent.java
@@ -28,12 +28,10 @@
 import java.util.Objects;
 
 /**
- * Content to be displayed on the account selector UI, including credential entries,
- * actions etc.
- *
- * @hide
+ * The content to be displayed on the account selector UI, including credential entries,
+ * actions etc. Returned as part of {@link GetCredentialsResponse}
  */
-public final class CredentialsDisplayContent implements Parcelable {
+public final class CredentialsResponseContent implements Parcelable {
     /** List of credential entries to be displayed on the UI. */
     private final @NonNull List<CredentialEntry> mCredentialEntries;
 
@@ -41,36 +39,36 @@
     private final @NonNull List<Action> mActions;
 
     /** Remote credential entry to get the response from a different device. */
-    private final @Nullable Action mRemoteCredentialEntry;
+    private final @Nullable CredentialEntry mRemoteCredentialEntry;
 
-    private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
+    private CredentialsResponseContent(@NonNull List<CredentialEntry> credentialEntries,
             @NonNull List<Action> actions,
-            @Nullable Action remoteCredentialEntry) {
+            @Nullable CredentialEntry remoteCredentialEntry) {
         mCredentialEntries = credentialEntries;
         mActions = actions;
         mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
-    private CredentialsDisplayContent(@NonNull Parcel in) {
+    private CredentialsResponseContent(@NonNull Parcel in) {
         List<CredentialEntry> credentialEntries = new ArrayList<>();
         in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
         mCredentialEntries = credentialEntries;
         List<Action> actions = new ArrayList<>();
         in.readTypedList(actions, Action.CREATOR);
         mActions = actions;
-        mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
+        mRemoteCredentialEntry = in.readTypedObject(CredentialEntry.CREATOR);
     }
 
-    public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
-            new Creator<CredentialsDisplayContent>() {
+    public static final @NonNull Creator<CredentialsResponseContent> CREATOR =
+            new Creator<CredentialsResponseContent>() {
                 @Override
-                public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
-                    return new CredentialsDisplayContent(in);
+                public CredentialsResponseContent createFromParcel(@NonNull Parcel in) {
+                    return new CredentialsResponseContent(in);
                 }
 
                 @Override
-                public CredentialsDisplayContent[] newArray(int size) {
-                    return new CredentialsDisplayContent[size];
+                public CredentialsResponseContent[] newArray(int size) {
+                    return new CredentialsResponseContent[size];
                 }
             };
 
@@ -103,22 +101,34 @@
     /**
      * Returns the remote credential entry to be displayed on the UI.
      */
-    public @Nullable Action getRemoteCredentialEntry() {
+    public @Nullable CredentialEntry getRemoteCredentialEntry() {
         return mRemoteCredentialEntry;
     }
 
     /**
-     * Builds an instance of {@link CredentialsDisplayContent}.
+     * Builds an instance of {@link CredentialsResponseContent}.
      */
     public static final class Builder {
         private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
         private List<Action> mActions = new ArrayList<>();
-        private Action mRemoteCredentialEntry;
+        private CredentialEntry mRemoteCredentialEntry;
 
         /**
-         * Sets the remote credential entry to be displayed on the UI.
+         * Sets a remote credential entry to be shown on the UI. Provider must set this if they
+         * wish to get the credential from a different device.
+         *
+         * <p> When constructing the {@link CredentialEntry} object, the {@code pendingIntent}
+         * must be set such that it leads to an activity that can provide UI to fulfill the request
+         * on a remote device. When user selects this {@code remoteCredentialEntry}, the system will
+         * invoke the {@code pendingIntent} set on the {@link CredentialEntry}.
+         *
+         * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+         * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+         * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} key should be populated
+         * with a {@link android.credentials.Credential} object.
          */
-        public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+        public @NonNull Builder setRemoteCredentialEntry(@Nullable CredentialEntry
+                remoteCredentialEntry) {
             mRemoteCredentialEntry = remoteCredentialEntry;
             return this;
         }
@@ -138,6 +148,11 @@
          * Adds an {@link Action} to the list of actions to be displayed on
          * the UI.
          *
+         * <p> An {@code action} must be used for independent user actions,
+         * such as opening the app, intenting directly into a certain app activity etc. The
+         * {@code pendingIntent} set with the {@code action} must invoke the corresponding
+         * activity.
+         *
          * @throws NullPointerException If {@code action} is null.
          */
         public @NonNull Builder addAction(@NonNull Action action) {
@@ -175,17 +190,16 @@
         /**
          * Builds a {@link GetCredentialsResponse} instance.
          *
-         * @throws NullPointerException If {@code credentialEntries} is null.
-         * @throws IllegalStateException if both {@code credentialEntries} and
-         * {@code actions} are empty.
+         * @throws IllegalStateException if {@code credentialEntries}, {@code actions}
+         * and {@code remoteCredentialEntry} are all null or empty.
          */
-        public @NonNull CredentialsDisplayContent build() {
+        public @NonNull CredentialsResponseContent build() {
             if (mCredentialEntries != null && mCredentialEntries.isEmpty()
-                    && mActions != null && mActions.isEmpty()) {
+                    && mActions != null && mActions.isEmpty() && mRemoteCredentialEntry == null) {
                 throw new IllegalStateException("credentialEntries and actions must not both "
                         + "be empty");
             }
-            return new CredentialsDisplayContent(mCredentialEntries, mActions,
+            return new CredentialsResponseContent(mCredentialEntries, mActions,
                     mRemoteCredentialEntry);
         }
     }
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index 03ba20e..9052b54 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -29,8 +29,6 @@
 
 /**
  * Request for getting user's credentials from a given credential provider.
- *
- * @hide
  */
 public final class GetCredentialsRequest implements Parcelable {
     /** Calling package of the app requesting for credentials. */
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
index 979a699..5263141 100644
--- a/core/java/android/service/credentials/GetCredentialsResponse.java
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -26,12 +26,10 @@
 /**
  * Response from a credential provider, containing credential entries and other associated
  * data to be shown on the account selector UI.
- *
- * @hide
  */
 public final class GetCredentialsResponse implements Parcelable {
     /** Content to be used for the UI. */
-    private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent;
+    private final @Nullable CredentialsResponseContent mCredentialsResponseContent;
 
     /**
      * Authentication action that must be launched and completed before showing any content
@@ -40,11 +38,17 @@
     private final @Nullable Action mAuthenticationAction;
 
     /**
-     * Creates a {@link GetCredentialsRequest} instance with an authentication action set.
+     * Creates a {@link GetCredentialsResponse} instance with an authentication {@link Action} set.
      * Providers must use this method when no content can be shown before authentication.
      *
-     * Once the authentication action activity is launched, and the user is authenticated, providers
-     * should create another response with {@link CredentialsDisplayContent} using
+     * <p> When the user selects this {@code authenticationAction}, the system invokes the
+     * corresponding {@code pendingIntent}. Once the authentication flow is complete,
+     * the {@link android.app.Activity} result should be set
+     * to {@link android.app.Activity#RESULT_OK} and the
+     * {@link CredentialProviderService#EXTRA_GET_CREDENTIALS_CONTENT_RESULT} extra should be set
+     * with a fully populated {@link CredentialsResponseContent} object.
+     * the authentication action activity is launched, and the user is authenticated, providers
+     * should create another response with {@link CredentialsResponseContent} using
      * {@code createWithDisplayContent}, and add that response to the result of the authentication
      * activity.
      *
@@ -58,27 +62,27 @@
     }
 
     /**
-     * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI.
+     * Creates a {@link GetCredentialsRequest} instance with content to be shown on the UI.
      * Providers must use this method when there is content to be shown without top level
-     * authentication required.
+     * authentication required, including credential entries, action entries or a remote entry,
      *
-     * @throws NullPointerException If {@code credentialsDisplayContent} is null.
+     * @throws NullPointerException If {@code credentialsResponseContent} is null.
      */
-    public static @NonNull GetCredentialsResponse createWithDisplayContent(
-            @NonNull CredentialsDisplayContent credentialsDisplayContent) {
-        Objects.requireNonNull(credentialsDisplayContent,
-                "credentialsDisplayContent must not be null");
-        return new GetCredentialsResponse(credentialsDisplayContent, null);
+    public static @NonNull GetCredentialsResponse createWithResponseContent(
+            @NonNull CredentialsResponseContent credentialsResponseContent) {
+        Objects.requireNonNull(credentialsResponseContent,
+                "credentialsResponseContent must not be null");
+        return new GetCredentialsResponse(credentialsResponseContent, null);
     }
 
-    private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent,
+    private GetCredentialsResponse(@Nullable CredentialsResponseContent credentialsResponseContent,
             @Nullable Action authenticationAction) {
-        mCredentialsDisplayContent = credentialsDisplayContent;
+        mCredentialsResponseContent = credentialsResponseContent;
         mAuthenticationAction = authenticationAction;
     }
 
     private GetCredentialsResponse(@NonNull Parcel in) {
-        mCredentialsDisplayContent = in.readTypedObject(CredentialsDisplayContent.CREATOR);
+        mCredentialsResponseContent = in.readTypedObject(CredentialsResponseContent.CREATOR);
         mAuthenticationAction = in.readTypedObject(Action.CREATOR);
     }
 
@@ -102,23 +106,23 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedObject(mCredentialsDisplayContent, flags);
+        dest.writeTypedObject(mCredentialsResponseContent, flags);
         dest.writeTypedObject(mAuthenticationAction, flags);
     }
 
     /**
-     * Returns the authentication action to be invoked before any other content
-     * can be shown to the user.
+     * If this response represents a top level authentication action, returns the authentication
+     * action to be invoked before any other content can be shown to the user.
      */
     public @Nullable Action getAuthenticationAction() {
         return mAuthenticationAction;
     }
 
     /**
-     * Returns the credentialDisplayContent that does not require authentication, and
-     * can be shown to the user on the account selector UI.
+     * Returns the actual content to be displayed on the selector, if this response does not
+     * require any top level authentication.
      */
-    public @Nullable CredentialsDisplayContent getCredentialsDisplayContent() {
-        return mCredentialsDisplayContent;
+    public @Nullable CredentialsResponseContent getCredentialsResponseContent() {
+        return mCredentialsResponseContent;
     }
 }
diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
new file mode 100644
index 0000000..ec0bc36
--- /dev/null
+++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.BeginCreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IBeginCreateCredentialCallback {
+    void onSuccess(in BeginCreateCredentialResponse request);
+    void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
deleted file mode 100644
index 4cc76a4..0000000
--- a/core/java/android/service/credentials/ICreateCredentialCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.service.credentials;
-
-import android.service.credentials.CreateCredentialResponse;
-
-/**
- * Interface from the system to a credential provider service.
- *
- * @hide
- */
-oneway interface ICreateCredentialCallback {
-    void onSuccess(in CreateCredentialResponse request);
-    void onFailure(int errorCode, in CharSequence message);
-}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c21cefa..b9eb3ed 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -18,9 +18,9 @@
 
 import android.os.ICancellationSignal;
 import android.service.credentials.GetCredentialsRequest;
-import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.IGetCredentialsCallback;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
 import android.os.ICancellationSignal;
 
 /**
@@ -30,5 +30,5 @@
  */
 interface ICredentialProviderService {
     ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
-    ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+    ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
 }
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
index bf8ee47..1c57700 100644
--- a/core/java/android/service/voice/HotwordAudioStream.java
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -92,41 +92,6 @@
         return new PersistableBundle();
     }
 
-    private String timestampToString() {
-        if (mTimestamp == null) {
-            return "";
-        }
-        return "TimeStamp:"
-                + " framePos=" + mTimestamp.framePosition
-                + " nanoTime=" + mTimestamp.nanoTime;
-    }
-
-    private void parcelTimestamp(Parcel dest, int flags) {
-        if (mTimestamp != null) {
-            // mTimestamp is not null, we write it to the parcel, set true.
-            dest.writeBoolean(true);
-            dest.writeLong(mTimestamp.framePosition);
-            dest.writeLong(mTimestamp.nanoTime);
-        } else {
-            // mTimestamp is null, we don't write any value out, set false.
-            dest.writeBoolean(false);
-        }
-    }
-
-    @Nullable
-    private static AudioTimestamp unparcelTimestamp(Parcel in) {
-        // If it is true, it means we wrote the value to the parcel before, parse it.
-        // Otherwise, return null.
-        if (in.readBoolean()) {
-            final AudioTimestamp timeStamp = new AudioTimestamp();
-            timeStamp.framePosition = in.readLong();
-            timeStamp.nanoTime = in.readLong();
-            return timeStamp;
-        } else {
-            return null;
-        }
-    }
-
     /**
      * Provides an instance of {@link Builder} with state corresponding to this instance.
      * @hide
@@ -229,7 +194,7 @@
         return "HotwordAudioStream { " +
                 "audioFormat = " + mAudioFormat + ", " +
                 "audioStreamParcelFileDescriptor = " + mAudioStreamParcelFileDescriptor + ", " +
-                "timestamp = " + timestampToString() + ", " +
+                "timestamp = " + mTimestamp + ", " +
                 "metadata = " + mMetadata +
         " }";
     }
@@ -278,7 +243,7 @@
         dest.writeByte(flg);
         dest.writeTypedObject(mAudioFormat, flags);
         dest.writeTypedObject(mAudioStreamParcelFileDescriptor, flags);
-        parcelTimestamp(dest, flags);
+        if (mTimestamp != null) dest.writeTypedObject(mTimestamp, flags);
         dest.writeTypedObject(mMetadata, flags);
     }
 
@@ -296,7 +261,7 @@
         byte flg = in.readByte();
         AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR);
         ParcelFileDescriptor audioStreamParcelFileDescriptor = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);
-        AudioTimestamp timestamp = unparcelTimestamp(in);
+        AudioTimestamp timestamp = (flg & 0x4) == 0 ? null : (AudioTimestamp) in.readTypedObject(AudioTimestamp.CREATOR);
         PersistableBundle metadata = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
 
         this.mAudioFormat = audioFormat;
@@ -449,10 +414,10 @@
     }
 
     @DataClass.Generated(
-            time = 1666342101364L,
+            time = 1669184301563L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java",
-            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\npublic  android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\npublic  android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index aebd91a..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2183,6 +2183,17 @@
         }
     }
 
+    /**
+     * Returns a Looper which messages such as {@link WallpaperService#DO_ATTACH},
+     * {@link WallpaperService#DO_DETACH} etc. are sent to.
+     * By default, returns the process's main looper.
+     * @hide
+     */
+    @NonNull
+    public Looper onProvideEngineLooper() {
+        return super.getMainLooper();
+    }
+
     private boolean isValid(RectF area) {
         if (area == null) return false;
         boolean valid = area.bottom > area.top && area.left < area.right
@@ -2215,12 +2226,12 @@
         Engine mEngine;
         @SetWallpaperFlags int mWhich;
 
-        IWallpaperEngineWrapper(WallpaperService context,
+        IWallpaperEngineWrapper(WallpaperService service,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                 int displayId, @SetWallpaperFlags int which) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
-            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
+            mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
             mConnection = conn;
             mWindowToken = windowToken;
             mWindowType = windowType;
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 517d982..959295b 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.FontScaleConverter;
 import android.os.SystemProperties;
 
 /**
@@ -273,6 +274,15 @@
      * increments at runtime based on a user preference for the font size.
      */
     public float scaledDensity;
+
+    /**
+     * If non-null, this will be used to calculate font sizes instead of {@link #scaledDensity}.
+     *
+     * @hide
+     */
+    @Nullable
+    public FontScaleConverter fontScaleConverter;
+
     /**
      * The exact physical pixels per inch of the screen in the X dimension.
      */
@@ -350,6 +360,7 @@
         noncompatScaledDensity = o.noncompatScaledDensity;
         noncompatXdpi = o.noncompatXdpi;
         noncompatYdpi = o.noncompatYdpi;
+        fontScaleConverter = o.fontScaleConverter;
     }
     
     public void setToDefaults() {
@@ -367,6 +378,7 @@
         noncompatScaledDensity = scaledDensity;
         noncompatXdpi = xdpi;
         noncompatYdpi = ydpi;
+        fontScaleConverter = null;
     }
 
     @Override
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4afd268..608cbda 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -122,6 +122,13 @@
     public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad";
 
     /**
+     * Enable trackpad gesture settings UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE =
+            "settings_new_keyboard_trackpad_gesture";
+
+    /**
      * Enable the new pages which is implemented with SPA.
      * @hide
      */
@@ -171,6 +178,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
@@ -190,6 +198,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
     }
 
     /**
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 7b28b8a..bc0e35d 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -234,4 +234,23 @@
     public int[] toArray() {
         return Arrays.copyOf(mValues, mSize);
     }
+
+    @Override
+    public String toString() {
+        // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call
+        // Arrays.toString() directly as it would return the unused elements as well)
+        int iMax = mSize - 1;
+        if (iMax == -1) {
+            return "[]";
+        }
+        StringBuilder b = new StringBuilder();
+        b.append('[');
+        for (int i = 0;; i++) {
+            b.append(mValues[i]);
+            if (i == iMax) {
+                return b.append(']').toString();
+            }
+            b.append(", ");
+        }
+    }
 }
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 44318bb..7e054fc 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -408,7 +408,14 @@
         case COMPLEX_UNIT_DIP:
             return value * metrics.density;
         case COMPLEX_UNIT_SP:
-            return value * metrics.scaledDensity;
+                if (metrics.fontScaleConverter != null) {
+                    return applyDimension(
+                            COMPLEX_UNIT_DIP,
+                            metrics.fontScaleConverter.convertSpToDp(value),
+                            metrics);
+                } else {
+                    return value * metrics.scaledDensity;
+                }
         case COMPLEX_UNIT_PT:
             return value * metrics.xdpi * (1.0f/72);
         case COMPLEX_UNIT_IN:
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5933ae4..fbca373 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -319,6 +319,19 @@
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10;
 
     /**
+     * Flag: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public static final int FLAG_OWN_FOCUS = 1 << 11;
+
+    /**
      * Display flag: Indicates that the contents of the display should not be scaled
      * to fit the physical screen dimensions.  Used for development only to emulate
      * devices with smaller physicals screens while preserving density.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e2bc566..0743ccb 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -738,9 +738,8 @@
      * If invoked through a package other than a launcher app, returns an empty list.
      *
      * @param displayId the id of the logical display
-     * @param packageName the name of the calling package
      */
-    List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+    List<DisplayInfo> getPossibleDisplayInfo(int displayId);
 
     /**
      * Called to show global actions.
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 2a8e7e4..080c0d8 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -533,6 +533,13 @@
         mHotSpotY = hotSpotY;
     }
 
+    @Override
+    public String toString() {
+        return "PointerIcon{type=" + typeToString(mType)
+                + ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY
+                + ", systemIconResourceId=" + mSystemIconResourceId + "}";
+    }
+
     private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
         if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
             throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
@@ -624,4 +631,40 @@
         displayManager.registerDisplayListener(sDisplayListener, null /* handler */);
     }
 
+    /**
+     * Convert type constant to string.
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_CUSTOM: return "CUSTOM";
+            case TYPE_NULL: return "NULL";
+            case TYPE_NOT_SPECIFIED: return "NOT_SPECIFIED";
+            case TYPE_ARROW: return "ARROW";
+            case TYPE_SPOT_HOVER: return "SPOT_HOVER";
+            case TYPE_SPOT_TOUCH: return "SPOT_TOUCH";
+            case TYPE_SPOT_ANCHOR: return "SPOT_ANCHOR";
+            case TYPE_CONTEXT_MENU: return "CONTEXT_MENU";
+            case TYPE_HAND: return "HAND";
+            case TYPE_HELP: return "HELP";
+            case TYPE_WAIT: return "WAIT";
+            case TYPE_CELL: return "CELL";
+            case TYPE_CROSSHAIR: return "CROSSHAIR";
+            case TYPE_TEXT: return "TEXT";
+            case TYPE_VERTICAL_TEXT: return "VERTICAL_TEXT";
+            case TYPE_ALIAS: return "ALIAS";
+            case TYPE_COPY: return "COPY";
+            case TYPE_NO_DROP: return "NO_DROP";
+            case TYPE_ALL_SCROLL: return "ALL_SCROLL";
+            case TYPE_HORIZONTAL_DOUBLE_ARROW: return "HORIZONTAL_DOUBLE_ARROW";
+            case TYPE_VERTICAL_DOUBLE_ARROW: return "VERTICAL_DOUBLE_ARROW";
+            case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW: return "TOP_RIGHT_DIAGONAL_DOUBLE_ARROW";
+            case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW: return "TOP_LEFT_DIAGONAL_DOUBLE_ARROW";
+            case TYPE_ZOOM_IN: return "ZOOM_IN";
+            case TYPE_ZOOM_OUT: return "ZOOM_OUT";
+            case TYPE_GRAB: return "GRAB";
+            case TYPE_GRABBING: return "GRABBING";
+            default: return Integer.toString(type);
+        }
+    }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ef18458..33ea92d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -728,8 +728,8 @@
 
     private void releaseSurfaces(boolean releaseSurfacePackage) {
         mAlpha = 1f;
+        mSurface.destroy();
         synchronized (mSurfaceControlLock) {
-            mSurface.destroy();
             if (mBlastBufferQueue != null) {
                 mBlastBufferQueue.destroy();
                 mBlastBufferQueue = null;
@@ -947,108 +947,112 @@
                     + " left=" + (mWindowSpaceLeft != mLocation[0])
                     + " top=" + (mWindowSpaceTop != mLocation[1]));
 
-            mVisible = mRequestedVisible;
-            mWindowSpaceLeft = mLocation[0];
-            mWindowSpaceTop = mLocation[1];
-            mSurfaceWidth = myWidth;
-            mSurfaceHeight = myHeight;
-            mFormat = mRequestedFormat;
-            mAlpha = alpha;
-            mLastWindowVisibility = mWindowVisibility;
-            mTransformHint = viewRoot.getBufferTransformHint();
-            mSubLayer = mRequestedSubLayer;
-
-            mScreenRect.left = mWindowSpaceLeft;
-            mScreenRect.top = mWindowSpaceTop;
-            mScreenRect.right = mWindowSpaceLeft + getWidth();
-            mScreenRect.bottom = mWindowSpaceTop + getHeight();
-            if (translator != null) {
-                translator.translateRectInAppWindowToScreen(mScreenRect);
-            }
-
-            final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
-            mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-            // Collect all geometry changes and apply these changes on the RenderThread worker
-            // via the RenderNode.PositionUpdateListener.
-            final Transaction surfaceUpdateTransaction = new Transaction();
-            if (creating) {
-                updateOpaqueFlag();
-                final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-                createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
-            } else if (mSurfaceControl == null) {
-                return;
-            }
-
-            final boolean redrawNeeded = sizeChanged || creating || hintChanged
-                    || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
-            boolean shouldSyncBuffer =
-                    redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
-            SyncBufferTransactionCallback syncBufferTransactionCallback = null;
-            if (shouldSyncBuffer) {
-                syncBufferTransactionCallback = new SyncBufferTransactionCallback();
-                mBlastBufferQueue.syncNextTransaction(
-                        false /* acquireSingleBuffer */,
-                        syncBufferTransactionCallback::onTransactionReady);
-            }
-
-            final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
-                    creating, sizeChanged, hintChanged, relativeZChanged,
-                    surfaceUpdateTransaction);
-
             try {
-                SurfaceHolder.Callback[] callbacks = null;
+                mVisible = mRequestedVisible;
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                mSurfaceWidth = myWidth;
+                mSurfaceHeight = myHeight;
+                mFormat = mRequestedFormat;
+                mAlpha = alpha;
+                mLastWindowVisibility = mWindowVisibility;
+                mTransformHint = viewRoot.getBufferTransformHint();
+                mSubLayer = mRequestedSubLayer;
 
-                final boolean surfaceChanged = creating;
-                if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
-                    mSurfaceCreated = false;
-                    notifySurfaceDestroyed();
+                mScreenRect.left = mWindowSpaceLeft;
+                mScreenRect.top = mWindowSpaceTop;
+                mScreenRect.right = mWindowSpaceLeft + getWidth();
+                mScreenRect.bottom = mWindowSpaceTop + getHeight();
+                if (translator != null) {
+                    translator.translateRectInAppWindowToScreen(mScreenRect);
                 }
 
-                copySurface(creating /* surfaceControlCreated */, sizeChanged);
+                final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
+                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+                // Collect all geometry changes and apply these changes on the RenderThread worker
+                // via the RenderNode.PositionUpdateListener.
+                final Transaction surfaceUpdateTransaction = new Transaction();
+                if (creating) {
+                    updateOpaqueFlag();
+                    final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+                    createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+                } else if (mSurfaceControl == null) {
+                    return;
+                }
 
-                if (mVisible && mSurface.isValid()) {
-                    if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
-                        mSurfaceCreated = true;
-                        mIsCreating = true;
-                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                + "visibleChanged -- surfaceCreated");
-                        callbacks = getSurfaceCallbacks();
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceCreated(mSurfaceHolder);
-                        }
+                final boolean redrawNeeded = sizeChanged || creating || hintChanged
+                        || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
+                boolean shouldSyncBuffer =
+                        redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+                SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+                if (shouldSyncBuffer) {
+                    syncBufferTransactionCallback = new SyncBufferTransactionCallback();
+                    mBlastBufferQueue.syncNextTransaction(
+                            false /* acquireSingleBuffer */,
+                            syncBufferTransactionCallback::onTransactionReady);
+                }
+
+                final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+                        creating, sizeChanged, hintChanged, relativeZChanged,
+                        surfaceUpdateTransaction);
+
+                try {
+                    SurfaceHolder.Callback[] callbacks = null;
+
+                    final boolean surfaceChanged = creating;
+                    if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
+                        mSurfaceCreated = false;
+                        notifySurfaceDestroyed();
                     }
-                    if (creating || formatChanged || sizeChanged || hintChanged
-                            || visibleChanged || realSizeChanged) {
-                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                + "surfaceChanged -- format=" + mFormat
-                                + " w=" + myWidth + " h=" + myHeight);
-                        if (callbacks == null) {
+
+                    copySurface(creating /* surfaceControlCreated */, sizeChanged);
+
+                    if (mVisible && mSurface.isValid()) {
+                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+                            mSurfaceCreated = true;
+                            mIsCreating = true;
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceCreated");
                             callbacks = getSurfaceCallbacks();
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceCreated(mSurfaceHolder);
+                            }
                         }
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                        if (creating || formatChanged || sizeChanged || hintChanged
+                                || visibleChanged || realSizeChanged) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceChanged -- format=" + mFormat
+                                    + " w=" + myWidth + " h=" + myHeight);
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                            }
                         }
-                    }
-                    if (redrawNeeded) {
-                        if (DEBUG) {
-                            Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
-                        }
-                        if (callbacks == null) {
-                            callbacks = getSurfaceCallbacks();
-                        }
+                        if (redrawNeeded) {
+                            if (DEBUG) {
+                                Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+                            }
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
 
-                        if (shouldSyncBuffer) {
-                            handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
-                        } else {
-                            handleSyncNoBuffer(callbacks);
+                            if (shouldSyncBuffer) {
+                                handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+                            } else {
+                                handleSyncNoBuffer(callbacks);
+                            }
                         }
                     }
+                } finally {
+                    mIsCreating = false;
+                    if (mSurfaceControl != null && !mSurfaceCreated) {
+                        releaseSurfaces(false /* releaseSurfacePackage*/);
+                    }
                 }
-            } finally {
-                mIsCreating = false;
-                if (mSurfaceControl != null && !mSurfaceCreated) {
-                    releaseSurfaces(false /* releaseSurfacePackage*/);
-                }
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception configuring surface", ex);
             }
             if (DEBUG) Log.v(
                 TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index d77e499..03b25c2 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,6 +29,7 @@
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
 import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
 import static android.view.WindowInsets.Type.systemBars;
@@ -597,7 +598,10 @@
         return new WindowInsets(null, null,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                displayCutoutCopyConstructorArgument(this),
+                // If the system window insets types contain displayCutout, we should also consume
+                // it.
+                (mCompatInsetsTypes & displayCutout()) != 0
+                        ? null : displayCutoutCopyConstructorArgument(this),
                 mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
                 mCompatIgnoreVisibility);
     }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 6dc9011..5c4305c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -366,7 +366,7 @@
         List<DisplayInfo> possibleDisplayInfos;
         try {
             possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
-                    .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+                    .getPossibleDisplayInfo(displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 85f5056..44b6deb 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -20,13 +20,18 @@
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ResolveInfo;
 import android.graphics.Region;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
@@ -34,14 +39,15 @@
 import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
 import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
  * Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to
  * interact with the windows in the display that this proxy represents. Proxying the default display
- * or a display that is not tracked will throw an exception. Only the real user has access to global
- * clients like SystemUI.
+ * or a display that is not tracked by accessibility, such as private displays, will throw an
+ * exception. Only the real user has access to global clients like SystemUI.
  *
  * <p>
  * To register and unregister a proxy, use
@@ -49,7 +55,16 @@
  * and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app
  * that has registered the proxy dies, the system will remove the proxy.
  *
- * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods
+ * <p>
+ * Avoid using the app's main thread. Proxy methods such as {@link #getWindows} and node methods
+ * like {@link AccessibilityNodeInfo#getChild(int)} will happen frequently. Node methods may also
+ * wait on the displayed app's UI thread to obtain accurate screen data.
+ *
+ * <p>
+ * To get a list of {@link AccessibilityServiceInfo}s that have populated {@link ComponentName}s and
+ * {@link ResolveInfo}s, retrieve the list using {@link #getInstalledAndEnabledServices()} after
+ * {@link #onProxyConnected()} has been called.
+ *
  * @hide
  */
 @SystemApi
@@ -91,7 +106,134 @@
     }
 
     /**
-     * An IAccessibilityServiceClient that handles interrupts and accessibility events.
+     * Handles {@link android.view.accessibility.AccessibilityEvent}s.
+     * <p>
+     * AccessibilityEvents represent changes to the UI, or what parts of the node tree have changed.
+     * AccessibilityDisplayProxy should use these to query new UI and send appropriate feedback
+     * to their users.
+     * <p>
+     * For example, a {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} indicates a change in windows,
+     * so a proxy may query {@link #getWindows} to obtain updated UI and potentially inform of a new
+     * window title. Or a proxy may emit an earcon on a
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+     */
+    public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+        // Default no-op
+    }
+
+    /**
+     * Handles a successful system connection after
+     * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} is called.
+     *
+     * <p>
+     * At this point, querying for UI is available and {@link AccessibilityEvent}s will begin being
+     * sent. An AccessibilityDisplayProxy may instantiate core infrastructure components here.
+     */
+    public void onProxyConnected() {
+        // Default no-op
+    }
+
+    /**
+     * Handles a request to interrupt the accessibility feedback.
+     * <p>
+     * AccessibilityDisplayProxy should interrupt the accessibility activity occurring on its
+     * display. For example, a screen reader may interrupt speech.
+     *
+     * @see AccessibilityManager#interrupt()
+     * @see AccessibilityService#onInterrupt()
+     */
+    public void interrupt() {
+        // Default no-op
+    }
+
+    /**
+     * Gets the focus of the window specified by {@code windowInfo}.
+     *
+     * @param windowInfo the window to search
+     * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+     * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+     * @return The node info of the focused view or null.
+     * @hide
+     * TODO(254545943): Do not expose until support for accessibility focus and/or input is in place
+     */
+    @Nullable
+    public AccessibilityNodeInfo findFocus(@NonNull AccessibilityWindowInfo windowInfo, int focus) {
+        AccessibilityNodeInfo windowRoot = windowInfo.getRoot();
+        return windowRoot != null ? windowRoot.findFocus(focus) : null;
+    }
+
+    /**
+     * Gets the windows of the tracked display.
+     *
+     * @see AccessibilityService#getWindows()
+     */
+    @NonNull
+    public List<AccessibilityWindowInfo> getWindows() {
+        return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId,
+                mDisplayId);
+    }
+
+    /**
+     * Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+     * {@link AccessibilityDisplayProxy}'s display.
+     *
+     * <p>These represent a11y features and services that are installed and running. These should
+     * not include {@link AccessibilityService}s installed on the phone.
+     *
+     * @param installedAndEnabledServices the list of installed and running a11y services.
+     */
+    public void setInstalledAndEnabledServices(
+            @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
+        mInstalledAndEnabledServices = installedAndEnabledServices;
+        sendServiceInfos();
+    }
+
+    /**
+     * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+     * properly set and there is an {@link IAccessibilityServiceConnection} to the
+     * AccessibilityManagerService.
+     */
+    private void sendServiceInfos() {
+        IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (mInstalledAndEnabledServices != null && mInstalledAndEnabledServices.size() > 0
+                && connection != null) {
+            try {
+                connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices);
+                AccessibilityInteractionClient.getInstance().clearCache(mConnectionId);
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfos", re);
+                re.rethrowFromSystemServer();
+            }
+        }
+        mInstalledAndEnabledServices = null;
+    }
+
+    /**
+     * Gets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+     * {@link AccessibilityDisplayProxy}'s display.
+     *
+     * @return The {@link AccessibilityServiceInfo}s of interested services.
+     * @see AccessibilityServiceInfo
+     */
+    @NonNull
+    public final List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
+        IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                return connection.getInstalledAndEnabledServices();
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+                re.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * An IAccessibilityServiceClient that handles interrupts, accessibility events, and system
+     * connection.
      */
     private class IAccessibilityServiceClientImpl extends
             AccessibilityService.IAccessibilityServiceClientWrapper {
@@ -100,17 +242,24 @@
             super(context, executor, new AccessibilityService.Callbacks() {
                 @Override
                 public void onAccessibilityEvent(AccessibilityEvent event) {
-                    // TODO: call AccessiiblityProxy.onAccessibilityEvent
+                    // TODO(254545943): Remove check when event processing is done more upstream in
+                    // AccessibilityManagerService.
+                    if (event.getDisplayId() == mDisplayId) {
+                        AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
+                    }
                 }
 
                 @Override
                 public void onInterrupt() {
-                    // TODO: call AccessiiblityProxy.onInterrupt
+                    AccessibilityDisplayProxy.this.interrupt();
                 }
+
                 @Override
                 public void onServiceConnected() {
-                    // TODO: send service infos and call AccessiiblityProxy.onProxyConnected
+                    AccessibilityDisplayProxy.this.sendServiceInfos();
+                    AccessibilityDisplayProxy.this.onProxyConnected();
                 }
+
                 @Override
                 public void init(int connectionId, IBinder windowToken) {
                     mConnectionId = connectionId;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 7030ab5..06a6de9 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -458,15 +458,21 @@
      * @return The {@link AccessibilityWindowInfo} list.
      */
     public List<AccessibilityWindowInfo> getWindows(int connectionId) {
-        final SparseArray<List<AccessibilityWindowInfo>> windows =
-                getWindowsOnAllDisplays(connectionId);
-        if (windows.size() > 0) {
-            return windows.valueAt(Display.DEFAULT_DISPLAY);
-        }
-        return Collections.emptyList();
+        return getWindowsOnDisplay(connectionId, Display.DEFAULT_DISPLAY);
     }
 
     /**
+     * Gets the info for all windows of the specified display.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @return The {@link AccessibilityWindowInfo} list belonging to {@code displayId}.
+     */
+    public List<AccessibilityWindowInfo> getWindowsOnDisplay(int connectionId, int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> windows =
+                getWindowsOnAllDisplays(connectionId);
+        return windows.get(displayId, Collections.emptyList());
+    }
+    /**
      * Gets the info for all windows of all displays.
      *
      * @param connectionId The id of a connection for interacting with the system.
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c2da638..a35e13e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,6 +421,53 @@
         return false;
     }
 
+    /**
+     * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+     * This includes root-leash and snapshots.
+     */
+    public void releaseAnimSurfaces() {
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            final Change c = mChanges.get(i);
+            if (c.mSnapshot != null) {
+                c.mSnapshot.release();
+                c.mSnapshot = null;
+            }
+        }
+        if (mRootLeash != null) {
+            mRootLeash.release();
+        }
+    }
+
+    /**
+     * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+     * if the surface-controls get stored and used elsewhere in the process. To just release
+     * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+     */
+    public void releaseAllSurfaces() {
+        releaseAnimSurfaces();
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            mChanges.get(i).getLeash().release();
+        }
+    }
+
+    /**
+     * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+     * refcounts are incremented which allows the "remote" receiver to release them without breaking
+     * the caller's references. Use this only if you need to "send" this to a local function which
+     * assumes it is being called from a remote caller.
+     */
+    public TransitionInfo localRemoteCopy() {
+        final TransitionInfo out = new TransitionInfo(mType, mFlags);
+        for (int i = 0; i < mChanges.size(); ++i) {
+            out.mChanges.add(mChanges.get(i).localRemoteCopy());
+        }
+        out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+        // Doesn't have any native stuff, so no need for actual copy
+        out.mOptions = mOptions;
+        out.mRootOffset.set(mRootOffset);
+        return out;
+    }
+
     /** Represents the change a WindowContainer undergoes during a transition */
     public static final class Change implements Parcelable {
         private final WindowContainerToken mContainer;
@@ -473,6 +520,27 @@
             mSnapshotLuma = in.readFloat();
         }
 
+        private Change localRemoteCopy() {
+            final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+            out.mParent = mParent;
+            out.mLastParent = mLastParent;
+            out.mMode = mMode;
+            out.mFlags = mFlags;
+            out.mStartAbsBounds.set(mStartAbsBounds);
+            out.mEndAbsBounds.set(mEndAbsBounds);
+            out.mEndRelOffset.set(mEndRelOffset);
+            out.mTaskInfo = mTaskInfo;
+            out.mAllowEnterPip = mAllowEnterPip;
+            out.mStartRotation = mStartRotation;
+            out.mEndRotation = mEndRotation;
+            out.mEndFixedRotation = mEndFixedRotation;
+            out.mRotationAnimation = mRotationAnimation;
+            out.mBackgroundColor = mBackgroundColor;
+            out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+            out.mSnapshotLuma = mSnapshotLuma;
+            return out;
+        }
+
         /** Sets the parent of this change's container. The parent must be a participant or null. */
         public void setParent(@Nullable WindowContainerToken parent) {
             mParent = parent;
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index fdc3e5a..f2ae973 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -146,7 +146,7 @@
 
     @SuppressLint("OnNameExpected")
     @Override
-    public void onConfigurationChanged(@Nullable Configuration configuration) {
+    public void onConfigurationChanged(@NonNull Configuration configuration) {
         // This is only called from WindowTokenClient.
         mCallbacksController.dispatchConfigurationChanged(configuration);
     }
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 76e068d..260d1a2 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -17,14 +17,20 @@
 package com.android.internal.content.om;
 
 import static android.content.Context.MODE_PRIVATE;
+import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY;
 
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
@@ -48,6 +54,7 @@
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 
@@ -129,10 +136,9 @@
         }
     }
 
-    /**
-     * Ensure the base dir for self-targeting is valid.
-     */
+    /** Ensure the base dir for self-targeting is valid. */
     @VisibleForTesting
+    @NonUiContext
     public void ensureBaseDir() {
         final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath();
         final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName();
@@ -170,13 +176,23 @@
         mBasePath = baseFile.toPath();
     }
 
+    private boolean isSameWithTargetSignature(final String targetPackage) {
+        final PackageManager packageManager = mContext.getPackageManager();
+        final String packageName = mContext.getPackageName();
+        if (TextUtils.equals(packageName, targetPackage)) {
+            return true;
+        }
+        return packageManager.checkSignatures(packageName, targetPackage)
+                == PackageManager.SIGNATURE_MATCH;
+    }
+
     /**
      * Check if the overlay name is valid or not.
      *
      * @param name the non-check overlay name
      * @return the valid overlay name
      */
-    private static String checkOverlayNameValid(@NonNull String name) {
+    public static String checkOverlayNameValid(@NonNull String name) {
         final String overlayName =
                 Preconditions.checkStringNotEmpty(
                         name, "overlayName should be neither empty nor null string");
@@ -202,8 +218,12 @@
     /**
      * Save FabricatedOverlay instance as frro and idmap files.
      *
+     * <p>In order to fill the overlayable policy, it's necessary to collect the information from
+     * app. And then, the information is passed to native layer to fill the overlayable policy
+     *
      * @param overlayInternal the FabricatedOverlayInternal to be saved.
      */
+    @NonUiContext
     public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal)
             throws IOException, PackageManager.NameNotFoundException {
         ensureBaseDir();
@@ -214,6 +234,9 @@
         final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
         checkPackageName(overlayInternal.packageName);
         checkPackageName(overlayInternal.targetPackageName);
+        Preconditions.checkStringNotEmpty(
+                overlayInternal.targetOverlayable,
+                "Target overlayable should be neither null nor empty string.");
 
         final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
         final String targetPackage = Preconditions.checkStringNotEmpty(
@@ -223,7 +246,17 @@
 
         createFrroFile(frroPath.toString(), overlayInternal);
         try {
-            createIdmapFile(targetPackage, frroPath.toString(), idmapPath.toString(), overlayName);
+            createIdmapFile(
+                    targetPackage,
+                    frroPath.toString(),
+                    idmapPath.toString(),
+                    overlayName,
+                    applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */,
+                    applicationInfo.isVendor(),
+                    applicationInfo.isProduct(),
+                    isSameWithTargetSignature(overlayInternal.targetPackageName),
+                    applicationInfo.isOdm(),
+                    applicationInfo.isOem());
         } catch (IOException e) {
             if (!frroPath.toFile().delete()) {
                 Log.w(TAG, "Failed to delete file " + frroPath);
@@ -237,6 +270,7 @@
      *
      * @param overlayName the specific name
      */
+    @NonUiContext
     public void unregisterFabricatedOverlay(@NonNull String overlayName) {
         ensureBaseDir();
         checkOverlayNameValid(overlayName);
@@ -252,6 +286,46 @@
     }
 
     /**
+     * Commit the overlay manager transaction
+     *
+     * @param transaction the overlay manager transaction
+     */
+    @NonUiContext
+    public void commit(@NonNull OverlayManagerTransaction transaction)
+            throws PackageManager.NameNotFoundException, IOException {
+        Objects.requireNonNull(transaction);
+
+        for (Iterator<OverlayManagerTransaction.Request> it = transaction.iterator();
+                it.hasNext(); ) {
+            final OverlayManagerTransaction.Request request = it.next();
+            if (request.type == TYPE_REGISTER_FABRICATED) {
+                final FabricatedOverlayInternal fabricatedOverlayInternal =
+                        Objects.requireNonNull(
+                                request.extras.getParcelable(
+                                        BUNDLE_FABRICATED_OVERLAY,
+                                        FabricatedOverlayInternal.class));
+
+                // populate the mandatory data
+                if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) {
+                    fabricatedOverlayInternal.packageName = mContext.getPackageName();
+                } else {
+                    if (!TextUtils.equals(
+                            fabricatedOverlayInternal.packageName, mContext.getPackageName())) {
+                        throw new IllegalArgumentException("Unknown package name in transaction");
+                    }
+                }
+
+                registerFabricatedOverlay(fabricatedOverlayInternal);
+            } else if (request.type == TYPE_UNREGISTER_FABRICATED) {
+                final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay);
+                unregisterFabricatedOverlay(overlayIdentifier.getOverlayName());
+            } else {
+                throw new IllegalArgumentException("Unknown request in transaction " + request);
+            }
+        }
+    }
+
+    /**
      * Get the list of overlays information for the target package name.
      *
      * @param targetPackage the target package name
@@ -315,7 +389,13 @@
             @NonNull String targetPath,
             @NonNull String overlayPath,
             @NonNull String idmapPath,
-            @NonNull String overlayName)
+            @NonNull String overlayName,
+            boolean isSystem,
+            boolean isVendor,
+            boolean isProduct,
+            boolean isSameWithTargetSignature,
+            boolean isOdm,
+            boolean isOem)
             throws IOException;
 
     private static native FabricatedOverlayInfo getFabricatedOverlayInfo(
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 1e3714e..8cb568d 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -40,7 +40,6 @@
     parcelable InitParams {
         IBinder token;
         IInputMethodPrivilegedOperations privilegedOperations;
-        int configChanges;
         int navigationBarFlags;
     }
 
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index 680f8fe..a587834 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -40,7 +40,8 @@
             TimeoutKind.SERVICE_START,
             TimeoutKind.SERVICE_EXEC,
             TimeoutKind.CONTENT_PROVIDER,
-            TimeoutKind.APP_REGISTERED})
+            TimeoutKind.APP_REGISTERED,
+            TimeoutKind.SHORT_FGS_TIMEOUT})
 
     @Retention(RetentionPolicy.SOURCE)
     public @interface TimeoutKind {
@@ -51,6 +52,7 @@
         int SERVICE_EXEC = 5;
         int CONTENT_PROVIDER = 6;
         int APP_REGISTERED = 7;
+        int SHORT_FGS_TIMEOUT = 8;
     }
 
     /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -144,4 +146,10 @@
     public static TimeoutRecord forApp(@NonNull String reason) {
         return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
     }
+
+    /** Record for a "short foreground service" timeout. */
+    @NonNull
+    public static TimeoutRecord forShortFgsTimeout(String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
+    }
 }
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index 1eb446e..f974d9d 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -174,6 +174,9 @@
         } else {
             dump.write("supported_modes", UsbPortProto.SUPPORTED_MODES, UsbPort.modeToString(mode));
         }
+        dump.write("supports_compliance_warnings",
+                UsbPortProto.SUPPORTS_COMPLIANCE_WARNINGS,
+                port.supportsComplianceWarnings());
 
         dump.end(token);
     }
@@ -250,6 +253,8 @@
                 status.isPowerTransferLimited());
         dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS,
                 UsbPort.powerBrickConnectionStatusToString(status.getPowerBrickConnectionStatus()));
+        dump.write("compliance_warning_status", UsbPortStatusProto.COMPLIANCE_WARNINGS_STRING,
+                UsbPort.complianceWarningsToString(status.getComplianceWarnings()));
         dump.end(token);
     }
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f140e79..1bc903a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,8 @@
 
     cppflags: ["-Wno-conversion-null"],
 
+    cpp_std: "gnu++20",
+
     srcs: [
         "android_animation_PropertyValuesHolder.cpp",
         "android_os_SystemClock.cpp",
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index a8d7231..29560dc 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -96,7 +96,7 @@
   }
 
   bool ForEachFile(const std::string& /* root_path */,
-                   const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+                   const std::function<void(StringPiece, FileType)>& /* f */) const {
     return true;
   }
 
@@ -402,7 +402,7 @@
     return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
 }
 
-static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+static jboolean NativeIsUpToDate(jlong ptr) {
     auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
     auto apk_assets = scoped_apk_assets->get();
     return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
@@ -500,24 +500,28 @@
 
 // JNI registration.
 static const JNINativeMethod gApkAssetsMethods[] = {
-    {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoad},
-    {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty},
-    {"nativeLoadFd",
-     "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoadFromFd},
-    {"nativeLoadFdOffsets",
-     "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoadFromFdOffset},
-    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
-    {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
-    {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
-    {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
-    {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
-    {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
-    {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
-     (void*)NativeGetOverlayableInfo},
-    {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
+        {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
+         (void*)NativeLoad},
+        {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J",
+         (void*)NativeLoadEmpty},
+        {"nativeLoadFd",
+         "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/"
+         "AssetsProvider;)J",
+         (void*)NativeLoadFromFd},
+        {"nativeLoadFdOffsets",
+         "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/"
+         "AssetsProvider;)J",
+         (void*)NativeLoadFromFdOffset},
+        {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+        {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+        {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
+        {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+        // @CriticalNative
+        {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+        {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+        {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
+         (void*)NativeGetOverlayableInfo},
+        {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
 };
 
 int register_android_content_res_ApkAssets(JNIEnv* env) {
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index cb97698..939a0e4 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -243,6 +243,23 @@
     }
 }
 
+static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId,
+                                    jobject sensorList) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    const ListOffsets &listOffsets(gListOffsets);
+
+    Vector<Sensor> nativeList;
+
+    mgr->getRuntimeSensorList(deviceId, nativeList);
+
+    ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size());
+    for (size_t i = 0; i < nativeList.size(); ++i) {
+        jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
+        // add to list
+        env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
+    }
+}
+
 static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
     SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
     return mgr->isDataInjectionEnabled();
@@ -503,40 +520,26 @@
 //----------------------------------------------------------------------------
 
 static const JNINativeMethod gSystemSensorManagerMethods[] = {
-    {"nativeClassInit",
-            "()V",
-            (void*)nativeClassInit },
-    {"nativeCreate",
-             "(Ljava/lang/String;)J",
-             (void*)nativeCreate },
+        {"nativeClassInit", "()V", (void *)nativeClassInit},
+        {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate},
 
-    {"nativeGetSensorAtIndex",
-            "(JLandroid/hardware/Sensor;I)Z",
-            (void*)nativeGetSensorAtIndex },
+        {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+         (void *)nativeGetSensorAtIndex},
 
-    {"nativeGetDynamicSensors",
-            "(JLjava/util/List;)V",
-            (void*)nativeGetDynamicSensors },
+        {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
 
-    {"nativeIsDataInjectionEnabled",
-            "(J)Z",
-            (void*)nativeIsDataInjectionEnabled },
+        {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
 
-    {"nativeCreateDirectChannel",
-            "(JJIILandroid/hardware/HardwareBuffer;)I",
-            (void*)nativeCreateDirectChannel },
+        {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
-    {"nativeDestroyDirectChannel",
-            "(JI)V",
-            (void*)nativeDestroyDirectChannel },
+        {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+         (void *)nativeCreateDirectChannel},
 
-    {"nativeConfigDirectChannel",
-            "(JIII)I",
-            (void*)nativeConfigDirectChannel },
+        {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
 
-    {"nativeSetOperationParameter",
-            "(JII[F[I)I",
-            (void*)nativeSetOperationParameter },
+        {"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel},
+
+        {"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter},
 };
 
 static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index df55e42..bba1760 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -123,8 +123,12 @@
 
     bool callCreateIdmapFile(std::string& out_error, const std::string& targetPath,
                              const std::string& overlayPath, const std::string& idmapPath,
-                             const std::string& overlayName) {
-        return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName);
+                             const std::string& overlayName, const bool isSystem,
+                             const bool isVendor, const bool isProduct,
+                             const bool isTargetSignature, const bool isOdm, const bool isOem) {
+        return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName,
+                                       isSystem, isVendor, isProduct, isTargetSignature, isOdm,
+                                       isOem);
     }
 
     bool callGetFabricatedOverlayInfo(std::string& out_error, const std::string& overlay_path,
@@ -158,7 +162,10 @@
     typedef bool (*CreateIdmapFileFunc)(std::string& out_error, const std::string& targetPath,
                                         const std::string& overlayPath,
                                         const std::string& idmapPath,
-                                        const std::string& overlayName);
+                                        const std::string& overlayName, const jboolean isSystem,
+                                        const jboolean isVendor, const jboolean isProduct,
+                                        const jboolean isSameWithTargetSignature,
+                                        const jboolean isOdm, const jboolean isOem);
 
     typedef bool (*GetFabricatedOverlayInfoFunc)(std::string& out_error,
                                                  const std::string& overlay_path,
@@ -295,7 +302,9 @@
 }
 
 static void CreateIdmapFile(JNIEnv* env, jclass /* clazz */, jstring jsTargetPath,
-                            jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName) {
+                            jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName,
+                            jboolean isSystem, jboolean isVendor, jboolean isProduct,
+                            jboolean isTargetSignature, jboolean isOdm, jboolean isOem) {
     DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
     if (!dlLoader) {
         jniThrowNullPointerException(env, "libidmap2 is not loaded");
@@ -327,7 +336,10 @@
 
     std::string err_result;
     if (!dlLoader.callCreateIdmapFile(err_result, targetPath.c_str(), overlayPath.c_str(),
-                                      idmapPath.c_str(), overlayName.c_str())) {
+                                      idmapPath.c_str(), overlayName.c_str(),
+                                      (isSystem == JNI_TRUE), (isVendor == JNI_TRUE),
+                                      (isProduct == JNI_TRUE), (isTargetSignature == JNI_TRUE),
+                                      (isOdm == JNI_TRUE), (isOem == JNI_TRUE))) {
         jniThrowException(env, kIOException, err_result.c_str());
         return;
     }
@@ -374,7 +386,7 @@
         {"createFrroFile", "(Ljava/lang/String;Landroid/os/FabricatedOverlayInternal;)V",
          reinterpret_cast<void*>(self_targeting::CreateFrroFile)},
         {"createIdmapFile",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZ)V",
          reinterpret_cast<void*>(self_targeting::CreateIdmapFile)},
         {"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;",
          reinterpret_cast<void*>(self_targeting::GetFabricatedOverlayInfo)},
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index df5e0a9..607fd10 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -240,6 +240,7 @@
     // ID of the port. A device (eg: Chromebooks) might have multiple ports.
     optional string id = 1;
     repeated Mode supported_modes = 2;
+    optional bool supports_compliance_warnings = 3;
 }
 
 message UsbPortStatusProto {
@@ -268,6 +269,7 @@
     optional string usb_data_status = 7;
     optional bool is_power_transfer_limited = 8;
     optional string usb_power_brick_status = 9;
+    optional string compliance_warnings_string = 10;
 }
 
 message UsbPortStatusRoleCombinationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5a7abcc..ad8f7fb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -293,6 +293,7 @@
 
     <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
+    <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
@@ -6286,12 +6287,12 @@
 
     <!-- Allows a regular application to use {@link android.app.Service#startForeground
          Service.startForeground} with the type "specialUse".
-         <p>Protection level: signature|appop|instant
+         <p>Protection level: normal|appop|instant
     -->
     <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
         android:description="@string/permdesc_foregroundServiceSpecialUse"
         android:label="@string/permlab_foregroundServiceSpecialUse"
-        android:protectionLevel="signature|appop|instant" />
+        android:protectionLevel="normal|appop|instant" />
 
     <!-- @SystemApi Allows to access all app shortcuts.
          @hide -->
@@ -7290,6 +7291,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.pm.GentleUpdateHelper$Service"
+            android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
                 android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d832bc..7946493 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8955,6 +8955,9 @@
         <!-- Flag indicating whether a recognition service can be selected as default. The default
              value of this flag is true. -->
         <attr name="selectableAsDefault" format="boolean" />
+        <!-- The maximal number of recognition sessions ongoing at the same time.
+             The default value is 1, meaning no concurrency. -->
+        <attr name="maxConcurrentSessionsCount" format="integer" />
     </declare-styleable>
 
     <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 86b0715..9c2643b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1828,6 +1828,14 @@
          config_enableFusedLocationOverlay is false. -->
     <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
 
+    <!-- If true will use the GNSS hardware implementation to service the GPS_PROVIDER. If false
+         will allow the GPS_PROVIDER to be replaced by an app at run-time (restricted to the package
+         specified by config_gnssLocationProviderPackageName). -->
+    <bool name="config_useGnssHardwareProvider" translatable="false">true</bool>
+    <!-- Package name providing GNSS location support. Used only when
+         config_useGnssHardwareProvider is false. -->
+    <string name="config_gnssLocationProviderPackageName" translatable="false">@null</string>
+
     <!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been
          set before. -->
     <bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool>
@@ -5354,6 +5362,12 @@
     <bool name="config_cecRoutingControlDisabled_allowed">true</bool>
     <bool name="config_cecRoutingControlDisabled_default">true</bool>
 
+    <bool name="config_cecSoundbarMode_userConfigurable">true</bool>
+    <bool name="config_cecSoundbarModeEnabled_allowed">true</bool>
+    <bool name="config_cecSoundbarModeEnabled_default">false</bool>
+    <bool name="config_cecSoundbarModeDisabled_allowed">true</bool>
+    <bool name="config_cecSoundbarModeDisabled_default">true</bool>
+
     <bool name="config_cecPowerControlMode_userConfigurable">true</bool>
     <bool name="config_cecPowerControlModeTv_allowed">true</bool>
     <bool name="config_cecPowerControlModeTv_default">false</bool>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 61229cb..bc5878a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -117,6 +117,7 @@
     <public name="accessibilityDataPrivate" />
     <public name="enableTextStylingShortcuts" />
     <public name="targetDisplayCategory"/>
+    <public name="maxConcurrentSessionsCount" />
   </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 1a86af0..cd93932 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1963,6 +1963,7 @@
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+  <java-symbol type="bool" name="config_useGnssHardwareProvider" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
   <java-symbol type="bool" name="config_enableGeofenceOverlay" />
   <java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -2125,6 +2126,7 @@
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+  <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
   <java-symbol type="string" name="config_geofenceProviderPackageName" />
   <java-symbol type="string" name="config_networkLocationProviderPackageName" />
@@ -4556,6 +4558,12 @@
   <java-symbol type="bool" name="config_cecRoutingControlDisabled_allowed" />
   <java-symbol type="bool" name="config_cecRoutingControlDisabled_default" />
 
+  <java-symbol type="bool" name="config_cecSoundbarMode_userConfigurable" />
+  <java-symbol type="bool" name="config_cecSoundbarModeEnabled_allowed" />
+  <java-symbol type="bool" name="config_cecSoundbarModeEnabled_default" />
+  <java-symbol type="bool" name="config_cecSoundbarModeDisabled_allowed" />
+  <java-symbol type="bool" name="config_cecSoundbarModeDisabled_default" />
+
   <java-symbol type="bool" name="config_cecPowerControlMode_userConfigurable" />
   <java-symbol type="bool" name="config_cecPowerControlModeTv_allowed" />
   <java-symbol type="bool" name="config_cecPowerControlModeTv_default" />
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index 6a01abe..f1ab696 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -17,7 +17,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.app.gamemanagertests"
-          android:sharedUserId="android.uid.system" >
+          android:sharedUserId="com.android.uid.test" >
+
+    <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
 
     <application android:appCategory="game">
         <uses-library android:name="android.test.runner" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 48cfc87..e811bb6 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -43,10 +43,12 @@
         "mockwebserver",
         "guava",
         "androidx.core_core",
+        "androidx.core_core-ktx",
         "androidx.test.espresso.core",
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "androidx.test.rules",
+        "junit-params",
         "kotlin-test",
         "mockito-target-minus-junit4",
         "ub-uiautomator",
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 4d5b0d2..561c10ba 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -21,7 +21,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent.IncludeExcludeRules;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
@@ -66,7 +67,7 @@
         excludePaths.add(path);
         IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
 
-        mBackupAgent = getAgentForOperationType(OperationType.BACKUP);
+        mBackupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
         when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
         when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
 
@@ -84,24 +85,26 @@
     @Test
     public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
         BackupAgent agent = new TestFullBackupAgent();
-        agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+        agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.BACKUP);
 
-        assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+        assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(
+                OperationType.BACKUP);
     }
 
     @Test
     public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
         BackupAgent agent = new TestFullBackupAgent();
-        agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+        agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.RESTORE);
 
-        assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+        assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(
+                OperationType.RESTORE);
     }
 
     @Test
     public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
             throws Exception {
         BackupAgent agent = new TestFullBackupAgent();
-        agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+        agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.BACKUP);
 
         // TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
         agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
@@ -110,9 +113,9 @@
                 .isEqualTo(DATA_TYPE_BACKED_UP);
     }
 
-    private BackupAgent getAgentForOperationType(@OperationType int operationType) {
+    private BackupAgent getAgentForBackupDestination(@BackupDestination int backupDestination) {
         BackupAgent agent = new TestFullBackupAgent();
-        agent.onCreate(USER_HANDLE, operationType);
+        agent.onCreate(USER_HANDLE, backupDestination);
         return agent;
     }
 
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index b9fdc6d..112d394 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -16,8 +16,8 @@
 
 package android.app.backup;
 
-import static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
-import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+import static android.app.backup.BackupAnnotations.OperationType.RESTORE;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
index a648a88..fc69f69 100644
--- a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
+++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
@@ -17,19 +17,26 @@
 package android.app.time;
 
 import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
 import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
 import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
 import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
 import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
 import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
 import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
 import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_NOT_APPLICABLE;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
 import android.service.timezone.TimeZoneProviderStatus;
@@ -207,4 +214,114 @@
         assertEquals(status,
                 LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
     }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_notRunning() {
+        LocationTimeZoneAlgorithmStatus notRunning =
+                new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                        PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+        assertFalse(notRunning.couldEnableTelephonyFallback());
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_unknown() {
+        // DETECTION_ALGORITHM_STATUS_UNKNOWN must never allow fallback
+        LocationTimeZoneAlgorithmStatus unknown =
+                new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+                        PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+        assertFalse(unknown.couldEnableTelephonyFallback());
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_notSupported() {
+        // DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED must never allow fallback
+        LocationTimeZoneAlgorithmStatus notSupported =
+                new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+                        PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+        assertFalse(notSupported.couldEnableTelephonyFallback());
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_running() {
+        // DETECTION_ALGORITHM_STATUS_RUNNING may allow fallback
+
+        // Sample provider-reported statuses that do / do not enable fallback.
+        TimeZoneProviderStatus enableTelephonyFallbackProviderStatus =
+                new TimeZoneProviderStatus.Builder()
+                        .setLocationDetectionDependencyStatus(
+                                DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+                        .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                        .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE)
+                        .build();
+        assertTrue(enableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback());
+
+        TimeZoneProviderStatus notEnableTelephonyFallbackProviderStatus =
+                new TimeZoneProviderStatus.Builder()
+                        .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                        .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                        .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE)
+                        .build();
+        assertFalse(notEnableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback());
+
+        // Provider not ready: Never enable fallback
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+
+        // Provider uncertain without reported status: Never enable fallback
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_READY, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+
+        // Provider uncertain with reported status: Fallback is based on the status for present
+        // providers that report their status. All present providers must have reported status and
+        // agree that fallback is a good idea.
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_NOT_READY, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_NOT_PRESENT, null);
+            assertTrue(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus);
+            assertTrue(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_IS_UNCERTAIN, notEnableTelephonyFallbackProviderStatus);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_NOT_PRESENT, null,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus);
+            assertTrue(status.couldEnableTelephonyFallback());
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
new file mode 100644
index 0000000..11afd04
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -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 android.companion.virtual.sensor;
+
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorConfigTest {
+
+    private static final String SENSOR_NAME = "VirtualSensorName";
+    private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private VirtualSensor.SensorStateChangeCallback mSensorCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void parcelAndUnparcel_matches() {
+        final VirtualSensorConfig originalConfig =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setVendor(SENSOR_VENDOR)
+                        .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+                        .build();
+        final Parcel parcel = Parcel.obtain();
+        originalConfig.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualSensorConfig recreatedConfig =
+                VirtualSensorConfig.CREATOR.createFromParcel(parcel);
+        assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
+        assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
+        assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+        assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
+    }
+
+    @Test
+    public void sensorConfig_onlyRequiredFields() {
+        final VirtualSensorConfig config =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
+        assertThat(config.getVendor()).isNull();
+        assertThat(config.getStateChangeCallback()).isNull();
+    }
+
+    @Test
+    public void sensorConfig_sensorCallbackInvocation() throws Exception {
+        final VirtualSensorConfig config =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+                        .build();
+
+        final Duration samplingPeriod = Duration.ofMillis(123);
+        final Duration batchLatency = Duration.ofMillis(456);
+
+        config.getStateChangeCallback().onStateChanged(true,
+                (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
+                (int) MILLISECONDS.toMicros(batchLatency.toMillis()));
+
+        verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
+    }
+}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
new file mode 100644
index 0000000..a9583fd
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorEventTest {
+
+    private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
+    private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
+
+    @Test
+    public void parcelAndUnparcel_matches() {
+        final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+                .setTimestampNanos(TIMESTAMP_NANOS)
+                .build();
+        final Parcel parcel = Parcel.obtain();
+        originalEvent.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualSensorEvent recreatedEvent =
+                VirtualSensorEvent.CREATOR.createFromParcel(parcel);
+        assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
+        assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
+    }
+
+    @Test
+    public void sensorEvent_nullValues() {
+        assertThrows(
+                IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
+    }
+
+    @Test
+    public void sensorEvent_noValues() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new VirtualSensorEvent.Builder(new float[0]).build());
+    }
+
+    @Test
+    public void sensorEvent_noTimestamp_usesCurrentTime() {
+        final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
+        assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+        assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
+        assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
+    }
+
+    @Test
+    public void sensorEvent_created() {
+        final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+                .setTimestampNanos(TIMESTAMP_NANOS)
+                .build();
+        assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
+        assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
new file mode 100644
index 0000000..cfca037
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.content.res
+
+import androidx.core.util.forEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterFactoryTest {
+
+    @Test
+    fun scale200IsTwiceAtSmallSizes() {
+        val table = FontScaleConverterFactory.forScale(2F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @SmallTest
+    fun missingLookupTableReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(3F)).isNull()
+    }
+
+    @SmallTest
+    fun missingLookupTable105ReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull()
+    }
+
+    @SmallTest
+    fun missingLookupTableNegativeReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(-1F)).isNull()
+    }
+
+    @SmallTest
+    fun unnecessaryFontScalesReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
+    }
+
+    @SmallTest
+    fun tablesMatchAndAreMonotonicallyIncreasing() {
+        FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+            assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
+            assertThat(lookupTable.mToDpValues).isNotEmpty()
+
+            assertThat(lookupTable.mFromSpValues.asList()).isInStrictOrder()
+            assertThat(lookupTable.mToDpValues.asList()).isInStrictOrder()
+
+            assertThat(lookupTable.mFromSpValues.asList()).containsNoDuplicates()
+            assertThat(lookupTable.mToDpValues.asList()).containsNoDuplicates()
+        }
+    }
+
+    companion object {
+        private const val CONVERSION_TOLERANCE = 0.05f
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
new file mode 100644
index 0000000..e405c55
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.content.res
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterTest {
+
+    @Test
+    fun straightInterpolation() {
+        val table = createTable(8f to 8f, 10f to 10f, 20f to 20f)
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+        assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun interpolate200Percent() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(60f)
+        assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(40f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun interpolate150Percent() {
+        val table = createTable(2f to 3f, 10f to 15f, 20f to 30f, 100f to 150f)
+        assertThat(table.convertSpToDp(2F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.5f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(12f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+        assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+        assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(75f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(7.5f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun pastEndsUsesLastScalingFactor() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(200f)
+        assertThat(table.convertSpToDp(31F)).isWithin(CONVERSION_TOLERANCE).of(62f)
+        assertThat(table.convertSpToDp(1000F)).isWithin(CONVERSION_TOLERANCE).of(2000f)
+        assertThat(table.convertSpToDp(2000F)).isWithin(CONVERSION_TOLERANCE).of(4000f)
+        assertThat(table.convertSpToDp(10000F)).isWithin(CONVERSION_TOLERANCE).of(20000f)
+    }
+
+    @Test
+    fun negativeSpIsNegativeDp() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        assertThat(table.convertSpToDp(-1F)).isWithin(CONVERSION_TOLERANCE).of(-2f)
+        assertThat(table.convertSpToDp(-8F)).isWithin(CONVERSION_TOLERANCE).of(-16f)
+        assertThat(table.convertSpToDp(-10F)).isWithin(CONVERSION_TOLERANCE).of(-20f)
+        assertThat(table.convertSpToDp(-30F)).isWithin(CONVERSION_TOLERANCE).of(-60f)
+        assertThat(table.convertSpToDp(-20F)).isWithin(CONVERSION_TOLERANCE).of(-40f)
+        assertThat(table.convertSpToDp(-5F)).isWithin(CONVERSION_TOLERANCE).of(-10f)
+        assertThat(table.convertSpToDp(-0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    private fun createTable(vararg pairs: Pair<Float, Float>) =
+        FontScaleConverter(
+            pairs.map { it.first }.toFloatArray(),
+            pairs.map { it.second }.toFloatArray()
+        )
+
+    companion object {
+        private const val CONVERSION_TOLERANCE = 0.05f
+    }
+}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index a528c19..6bf8f56 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -203,16 +203,22 @@
                 fallbackMap);
         SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
         Map<String, Typeface> copiedFontMap = new ArrayMap<>();
-        Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
-                copiedFontMap);
-        assertEquals(systemFontMap.size(), copiedFontMap.size());
-        for (String key : systemFontMap.keySet()) {
-            assertTrue(copiedFontMap.containsKey(key));
-            Typeface original = systemFontMap.get(key);
-            Typeface copied = copiedFontMap.get(key);
-            assertEquals(original.getStyle(), copied.getStyle());
-            assertEquals(original.getWeight(), copied.getWeight());
-            assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+        try {
+            Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
+                    copiedFontMap);
+            assertEquals(systemFontMap.size(), copiedFontMap.size());
+            for (String key : systemFontMap.keySet()) {
+                assertTrue(copiedFontMap.containsKey(key));
+                Typeface original = systemFontMap.get(key);
+                Typeface copied = copiedFontMap.get(key);
+                assertEquals(original.getStyle(), copied.getStyle());
+                assertEquals(original.getWeight(), copied.getWeight());
+                assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+            }
+        } finally {
+            for (Typeface typeface : copiedFontMap.values()) {
+                typeface.releaseNativeObjectForTest();
+            }
         }
     }
 
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index c0325ca..8e63a0f 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -47,29 +47,6 @@
         return InstrumentationRegistry.getContext();
     }
 
-    /**
-     * Sets {@code mode} for the given {@code ops} and the given {@code uid}.
-     *
-     * <p>This method drops shell permission identity.
-     */
-    private static void setAppOpsModeForUid(int uid, int mode, String... ops) {
-        if (ops == null) {
-            return;
-        }
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity();
-        try {
-            for (String op : ops) {
-                getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
-            }
-        } finally {
-            InstrumentationRegistry.getInstrumentation()
-                    .getUiAutomation()
-                    .dropShellPermissionIdentity();
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         dir = getContext().getDir("testing", Context.MODE_PRIVATE);
@@ -127,17 +104,4 @@
         Environment.buildPath(dir, "Taxes.pdf").createNewFile();
         assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir));
     }
-
-    @Test
-    public void testIsExternalStorageManager() throws Exception {
-        assertFalse(Environment.isExternalStorageManager());
-        try {
-            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED,
-                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
-            assertTrue(Environment.isExternalStorageManager());
-        } finally {
-            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_DEFAULT,
-                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index f7ca822..0c7ff4a 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import static android.os.VibrationEffect.DEFAULT_AMPLITUDE;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
@@ -48,6 +49,7 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.time.Duration;
+import java.util.Arrays;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -62,16 +64,363 @@
     private static final int TEST_AMPLITUDE = 100;
     private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
     private static final int[] TEST_AMPLITUDES =
-            new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+            new int[] { 255, 0, DEFAULT_AMPLITUDE };
 
     private static final VibrationEffect TEST_ONE_SHOT =
             VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
     private static final VibrationEffect DEFAULT_ONE_SHOT =
-            VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+            VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE);
     private static final VibrationEffect TEST_WAVEFORM =
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
 
     @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7},
+                /* amplitudes= */ new int[] {
+                        0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3, 4, 11, 7};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 3);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ 1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {200},
+                /* repeatIndex= */ -1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_nonZeroTimings() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_oneValue() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 0, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 0, 1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 0, 1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_sparsedZeroes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4},
+                /* repeatIndex= */ 2);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_notPatternPased() {
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_defaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE);
+        long[] expectedPattern = new long[] {0, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ 50);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_noOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .compose();
+        long[] expectedPattern = new long[] {7, 33, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {30, 40},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(10))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(5))
+                .compose();
+        long[] expectedPattern = new long[] {30, 90, 14, 5, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withPrimitives() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_repeating() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .repeatEffectIndefinitely(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_effectsViaStartWaveform() {
+        // Effects created via startWaveform are not expected to be converted to long[] patterns, as
+        // they are not configured to always play with the default amplitude.
+        VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(80), targetAmplitude(1))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetFrequency(50))
+                .addSustain(Duration.ofMillis(50))
+                .addTransition(Duration.ofMillis(20), targetFrequency(75))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
     public void getRingtones_noPrebakedRingtones() {
         Resources r = mockRingtoneResources(new String[0]);
         Context context = mockContext(r);
@@ -100,7 +449,7 @@
     @Test
     public void testValidateOneShot() {
         VibrationEffect.createOneShot(1, 255).validate();
-        VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate();
+        VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate();
 
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createOneShot(-1, 255).validate());
@@ -501,6 +850,13 @@
         assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
     }
 
+    private void assertArrayEq(long[] expected, long[] actual) {
+        assertTrue(
+                String.format("Expected pattern %s, but was %s",
+                        Arrays.toString(expected), Arrays.toString(actual)),
+                Arrays.equals(expected, actual));
+    }
+
     private Resources mockRingtoneResources() {
         return mockRingtoneResources(new String[]{
                 RINGTONE_URI_1,
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index 9006cd9..0c1630e 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,15 +16,31 @@
 
 package android.service.timezone;
 
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
 
 import static org.junit.Assert.assertEquals;
 
+import android.service.timezone.TimeZoneProviderStatus.DependencyStatus;
+import android.service.timezone.TimeZoneProviderStatus.OperationStatus;
+
 import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
 
 /** Non-SDK tests. See CTS for SDK API tests. */
+@RunWith(JUnitParamsRunner.class)
 public class TimeZoneProviderStatusTest {
 
     @Test
@@ -37,4 +53,51 @@
 
         assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
     }
+
+    @Test
+    @Parameters(method = "couldEnableTelephonyFallbackParams")
+    public void couldEnableTelephonyFallback(@DependencyStatus int locationDetectionStatus,
+            @DependencyStatus int connectivityStatus, @OperationStatus int tzResolutionStatus) {
+        TimeZoneProviderStatus providerStatus =
+                new TimeZoneProviderStatus.Builder()
+                        .setLocationDetectionDependencyStatus(locationDetectionStatus)
+                        .setConnectivityDependencyStatus(connectivityStatus)
+                        .setTimeZoneResolutionOperationStatus(tzResolutionStatus)
+                        .build();
+        boolean locationDetectionStatusCouldEnableFallback =
+                (locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                        || locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+        boolean connectivityStatusCouldEnableFallback =
+                (connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                        || connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+        boolean tzResolutionStatusCouldEnableFallback = false;
+
+        assertEquals(locationDetectionStatusCouldEnableFallback
+                        || connectivityStatusCouldEnableFallback
+                        || tzResolutionStatusCouldEnableFallback,
+                providerStatus.couldEnableTelephonyFallback());
+    }
+
+    public static Integer[][] couldEnableTelephonyFallbackParams() {
+        List<Integer[]> params = new ArrayList<>();
+        @DependencyStatus int[] dependencyStatuses =
+                IntStream.rangeClosed(
+                        DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS).toArray();
+        @OperationStatus int[] operationStatuses =
+                IntStream.rangeClosed(OPERATION_STATUS_UNKNOWN, OPERATION_STATUS_FAILED).toArray();
+
+        // Cartesian product: dependencyStatus x dependencyStatus x operationStatus
+        for (@DependencyStatus int locationDetectionStatus : dependencyStatuses) {
+            for (@DependencyStatus int connectivityStatus : dependencyStatuses) {
+                for (@OperationStatus int tzResolutionStatus : operationStatuses) {
+                    params.add(new Integer[] {
+                            locationDetectionStatus,
+                            connectivityStatus,
+                            tzResolutionStatus
+                    });
+                }
+            }
+        }
+        return params.toArray(new Integer[0][0]);
+    }
 }
diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt
index 7a05d97..7d98a7d 100644
--- a/core/tests/coretests/src/android/util/TypedValueTest.kt
+++ b/core/tests/coretests/src/android/util/TypedValueTest.kt
@@ -16,16 +16,17 @@
 
 package android.util
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.abs
+import kotlin.math.min
+import kotlin.math.roundToInt
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
-import kotlin.math.abs
-import kotlin.math.min
-import kotlin.math.roundToInt
 
 @RunWith(AndroidJUnit4::class)
 class TypedValueTest {
@@ -152,4 +153,19 @@
         val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics)
         assertEquals(widthFloat.roundToInt(), widthPx)
     }
-}
\ No newline at end of file
+
+    @SmallTest
+    @Test
+    fun testNonLinearFontScaling_nullLookupFallsBackToScaledDensity() {
+        val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
+        val fontScale = 2f
+        metrics.density = 1f
+        metrics.scaledDensity = fontScale * metrics.density
+        metrics.fontScaleConverter = null
+
+        assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics))
+                .isEqualTo(20f)
+        assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50f, metrics))
+                .isEqualTo(100f)
+    }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index ee1e10f..7cbf3ffa 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -263,10 +263,24 @@
     }
 
     private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
-        // TODO(241429275): Will override A11yProxy methods in the future.
         MyAccessibilityProxy(int displayId,
                 @NonNull List<AccessibilityServiceInfo> serviceInfos) {
             super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
         }
+
+        @Override
+        public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+
+        }
+
+        @Override
+        public void onProxyConnected() {
+
+        }
+
+        @Override
+        public void interrupt() {
+
+        }
     }
 }
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
index 82998db..063c569 100644
--- a/core/tests/overlaytests/device_self_targeting/Android.bp
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -29,6 +29,7 @@
         "androidx.test.rules",
         "androidx.test.runner",
         "androidx.test.ext.junit",
+        "mockito-target-minus-junit4",
         "truth-prebuilt",
     ],
 
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
new file mode 100644
index 0000000..5cc214d
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
@@ -0,0 +1,84 @@
+<?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>
+    <overlayable name="PublicOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="public">
+            <item type="color" name="public_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="SignatureOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="signature">
+            <item type="color" name="mycolor" />
+            <item type="color" name="signature_overlayable_color" />
+            <item type="string" name="mystring" />
+            <item type="drawable" name="mydrawable" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="SystemAppOverlayable" actor="overlay://theme">
+        <!-- The app in system partition can overlay the below resources -->
+        <policy type="system">
+            <item type="color" name="system_app_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="OdmOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="odm">
+            <item type="color" name="odm_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="OemOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="oem">
+            <item type="color" name="oem_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="VendorOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="vendor">
+            <item type="color" name="vendor_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="ProductOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="product">
+            <item type="color" name="product_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="ActorOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="actor">
+            <item type="color" name="actor_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="ConfigOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="config_signature">
+            <item type="color" name="config_overlayable_color" />
+        </policy>
+    </overlayable>
+
+</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/values.xml b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
index f0b4a6f..d82de97 100644
--- a/core/tests/overlaytests/device_self_targeting/res/values/values.xml
+++ b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
@@ -17,4 +17,14 @@
 <resources>
     <color name="mycolor">#ff112233</color>
     <string name="mystring">hello</string>
+
+    <color name="public_overlayable_color">#000</color>
+    <color name="signature_overlayable_color">#000</color>
+    <color name="system_app_overlayable_color">#000</color>
+    <color name="odm_overlayable_color">#000</color>
+    <color name="oem_overlayable_color">#000</color>
+    <color name="vendor_overlayable_color">#000</color>
+    <color name="product_overlayable_color">#000</color>
+    <color name="actor_overlayable_color">#000</color>
+    <color name="config_overlayable_color">#000</color>
 </resources>
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index ca58410..40d0bef 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -17,15 +17,24 @@
 package com.android.overlaytest;
 
 import static android.content.Context.MODE_PRIVATE;
+import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
 
 import static com.android.internal.content.om.OverlayManagerImpl.SELF_TARGET;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.om.FabricatedOverlay;
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.os.FabricatedOverlayInternal;
@@ -72,11 +81,23 @@
     private static final String TARGET_COLOR_RES = "color/mycolor";
     private static final String TARGET_STRING_RES = "string/mystring";
     private static final String TARGET_DRAWABLE_RES = "drawable/mydrawable";
+    private static final String PUBLIC_OVERLAYABLE = "PublicOverlayable";
+    private static final String SIGNATURE_OVERLAYABLE = "SignatureOverlayable";
+    private static final String SYSTEM_APP_OVERLAYABLE = "SystemAppOverlayable";
+    private static final String ODM_OVERLAYABLE = "OdmOverlayable";
+    private static final String OEM_OVERLAYABLE = "OemOverlayable";
+    private static final String VENDOR_OVERLAYABLE = "VendorOverlayable";
+    private static final String PRODUCT_OVERLAYABLE = "ProductOverlayable";
+    private static final String ACTOR_OVERLAYABLE = "ActorOverlayable";
+    private static final String CONFIG_OVERLAYABLE = "ConfigOverlayable";
 
     private Context mContext;
     private OverlayManagerImpl mOverlayManagerImpl;
     private String mOverlayName;
 
+    private PackageManager mMockPackageManager;
+    private ApplicationInfo mMockApplicationInfo;
+
     @Rule public TestName mTestName = new TestName();
 
     @Rule public Expect expect = Expect.create();
@@ -111,7 +132,36 @@
     public void setUp() throws IOException {
         clearDir();
         mOverlayName = mTestName.getMethodName();
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mMockApplicationInfo = mock(ApplicationInfo.class);
+        when(mMockApplicationInfo.isSystemApp()).thenReturn(false);
+        when(mMockApplicationInfo.isSystemExt()).thenReturn(false);
+        when(mMockApplicationInfo.isOdm()).thenReturn(false);
+        when(mMockApplicationInfo.isOem()).thenReturn(false);
+        when(mMockApplicationInfo.isVendor()).thenReturn(false);
+        when(mMockApplicationInfo.isProduct()).thenReturn(false);
+        when(mMockApplicationInfo.getBaseCodePath()).thenReturn(
+                context.getApplicationInfo().getBaseCodePath());
+        mMockApplicationInfo.sourceDir = context.getApplicationInfo().sourceDir;
+
+        mMockPackageManager = mock(PackageManager.class);
+        when(mMockPackageManager.checkSignatures(anyString(), anyString()))
+                .thenReturn(SIGNATURE_NO_MATCH);
+
+        mContext =
+                new ContextWrapper(context) {
+                    @Override
+                    public ApplicationInfo getApplicationInfo() {
+                        return mMockApplicationInfo;
+                    }
+
+                    @Override
+                    public PackageManager getPackageManager() {
+                        return mMockPackageManager;
+                    }
+                };
+
         mOverlayManagerImpl = new OverlayManagerImpl(mContext);
     }
 
@@ -144,12 +194,14 @@
 
     private <T> FabricatedOverlayInternal createOverlayWithName(
             @NonNull String overlayName,
+            @NonNull String targetOverlayable,
             @NonNull String targetPackageName,
             @NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
         final String packageName = mContext.getPackageName();
         FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
         overlayInternal.overlayName = overlayName;
         overlayInternal.targetPackageName = targetPackageName;
+        overlayInternal.targetOverlayable = targetOverlayable;
         overlayInternal.packageName = packageName;
 
         addOverlayEntry(overlayInternal, entryDefinitions);
@@ -162,6 +214,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SYSTEM_APP_OVERLAYABLE,
                         "android",
                         List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
 
@@ -190,6 +243,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
 
@@ -215,6 +269,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_STRING_RES, Pair.create(null, "HELLO"))));
 
@@ -242,6 +297,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_DRAWABLE_RES,
                                             Pair.create(null, parcelFileDescriptor))));
@@ -268,6 +324,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create("color/not_existed", Pair.create(null, "HELLO"))));
 
@@ -301,6 +358,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
         mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -309,6 +367,7 @@
         overlayInternal =
                 createOverlayWithName(
                         secondOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
         mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -341,6 +400,7 @@
         FabricatedOverlayInternal overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
         mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -349,6 +409,7 @@
         overlayInternal =
                 createOverlayWithName(
                         mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
                         mContext.getPackageName(),
                         List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
         mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -392,6 +453,40 @@
     }
 
     @Test
+    public void commit_withNullTransaction_shouldFail() {
+        assertThrows(NullPointerException.class, () -> mOverlayManagerImpl.commit(null));
+    }
+
+    @Test
+    public void commitRegisterOverlay_fromOtherBuilder_shouldWork()
+            throws PackageManager.NameNotFoundException, IOException {
+        FabricatedOverlay overlay =
+                new FabricatedOverlay.Builder(
+                                mContext.getPackageName(), mOverlayName, mContext.getPackageName())
+                        .setTargetOverlayable(SIGNATURE_OVERLAYABLE)
+                        .setResourceValue(
+                                TARGET_COLOR_RES, TypedValue.TYPE_INT_COLOR_ARGB8, Color.WHITE)
+                        .build();
+        OverlayManagerTransaction transaction =
+                new OverlayManagerTransaction.Builder().registerFabricatedOverlay(overlay).build();
+
+        mOverlayManagerImpl.commit(transaction);
+
+        final List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+        final int firstNumberOfOverlays = overlayInfos.size();
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        final OverlayInfo overlayInfo = overlayInfos.get(0);
+        expect.that(overlayInfo).isNotNull();
+        Truth.assertThat(expect.hasFailures()).isFalse();
+        expect.that(overlayInfo.isFabricated()).isTrue();
+        expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+        expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+    }
+
+    @Test
     public void newOverlayManagerImpl_forOtherUser_shouldFail() {
         Context fakeContext =
                 new ContextWrapper(mContext) {
@@ -408,4 +503,177 @@
 
         assertThrows(SecurityException.class, () -> new OverlayManagerImpl(fakeContext));
     }
+
+    FabricatedOverlayInternal prepareFabricatedOverlayInternal(
+            String targetOverlayableName, String targetEntryName) {
+        return createOverlayWithName(
+                mOverlayName,
+                targetOverlayableName,
+                mContext.getPackageName(),
+                List.of(
+                        Pair.create(
+                                targetEntryName,
+                                Pair.create(null, Color.WHITE))));
+    }
+
+    @Test
+    public void registerOverlayOnSystemOverlayable_selfIsNotSystemApp_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                SYSTEM_APP_OVERLAYABLE,
+                "color/system_app_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnOdmOverlayable_selfIsNotOdm_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                ODM_OVERLAYABLE,
+                "color/odm_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnOemOverlayable_selfIsNotOem_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                OEM_OVERLAYABLE,
+                "color/oem_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnVendorOverlayable_selfIsNotVendor_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                VENDOR_OVERLAYABLE,
+                "color/vendor_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnProductOverlayable_selfIsNotProduct_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                PRODUCT_OVERLAYABLE,
+                "color/product_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnActorOverlayable_notSupport_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                ACTOR_OVERLAYABLE,
+                "color/actor_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnConfigOverlayable_notSupport_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                CONFIG_OVERLAYABLE,
+                "color/config_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnPublicOverlayable_shouldAlwaysSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                PUBLIC_OVERLAYABLE,
+                "color/public_overlayable_color");
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnSystemOverlayable_selfIsSystemApp_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                SYSTEM_APP_OVERLAYABLE,
+                "color/system_app_overlayable_color");
+        when(mMockApplicationInfo.isSystemApp()).thenReturn(true);
+        when(mMockApplicationInfo.isSystemExt()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnOdmOverlayable_selfIsOdm_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                ODM_OVERLAYABLE,
+                "color/odm_overlayable_color");
+        when(mMockApplicationInfo.isOdm()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnOemOverlayable_selfIsOem_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                OEM_OVERLAYABLE,
+                "color/oem_overlayable_color");
+        when(mMockApplicationInfo.isOem()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnVendorOverlayable_selfIsVendor_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                VENDOR_OVERLAYABLE,
+                "color/vendor_overlayable_color");
+        when(mMockApplicationInfo.isVendor()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnProductOverlayable_selfIsProduct_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                PRODUCT_OVERLAYABLE,
+                "color/product_overlayable_color");
+        when(mMockApplicationInfo.isProduct()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
 }
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
index a76c640..caa7312 100644
--- a/core/tests/utiltests/src/android/util/IntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -16,8 +16,8 @@
 
 package android.util;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -25,6 +25,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IntArrayTest {
@@ -35,51 +37,65 @@
         a.add(1);
         a.add(2);
         a.add(3);
-        verify(new int[]{1, 2, 3}, a);
+        verify(a, 1, 2, 3);
 
         IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
         a.addAll(b);
-        verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+        verify(a, 1, 2, 3, 4, 5, 6);
 
         a.resize(2);
-        verify(new int[]{1, 2}, a);
+        verify(a, 1, 2);
 
         a.resize(8);
-        verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 0, 0, 0);
 
         a.set(5, 10);
-        verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 10, 0, 0);
 
         a.add(5, 20);
-        assertEquals(20, a.get(5));
-        assertEquals(5, a.indexOf(20));
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+        assertThat(a.get(5)).isEqualTo(20);
+        assertThat(a.indexOf(20)).isEqualTo(5);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0);
 
-        assertEquals(-1, a.indexOf(99));
+        assertThat(a.indexOf(99)).isEqualTo(-1);
 
         a.resize(15);
         a.set(14, 30);
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30);
 
         int[] backingArray = new int[]{1, 2, 3, 4};
         a = IntArray.wrap(backingArray);
         a.set(0, 10);
-        assertEquals(10, backingArray[0]);
+        assertThat(backingArray[0]).isEqualTo(10);
         backingArray[1] = 20;
         backingArray[2] = 30;
-        verify(backingArray, a);
-        assertEquals(2, a.indexOf(30));
+        verify(a, backingArray);
+        assertThat(a.indexOf(30)).isEqualTo(2);
 
         a.resize(2);
-        assertEquals(0, backingArray[2]);
-        assertEquals(0, backingArray[3]);
+        assertThat(backingArray[2]).isEqualTo(0);
+        assertThat(backingArray[3]).isEqualTo(0);
 
         a.add(50);
-        verify(new int[]{10, 20, 50}, a);
+        verify(a, 10, 20, 50);
     }
 
-    public void verify(int[] expected, IntArray intArray) {
-        assertEquals(expected.length, intArray.size());
-        assertArrayEquals(expected, intArray.toArray());
+    @Test
+    public void testToString() {
+        IntArray a = new IntArray(10);
+        a.add(4);
+        a.add(8);
+        a.add(15);
+        a.add(16);
+        a.add(23);
+        a.add(42);
+
+        assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42");
+        assertWithMessage("toString()").that(a.toString()).doesNotContain("0");
+    }
+
+    public void verify(IntArray intArray, int... expected) {
+        assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList()
+                .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList());
     }
 }
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 215b60e..a43e225 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -371,6 +371,10 @@
 # key 413 "KEY_DIGITS"
 # key 414 "KEY_TEEN"
 # key 415 "KEY_TWEN"
+# key 418 "KEY_ZOOM_IN"
+key 418   ZOOM_IN
+# key 419 "KEY_ZOOM_OUT"
+key 419   ZOOM_OUT
 key 528 FOCUS
 
 key 429   CONTACTS
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
new file mode 100644
index 0000000..f0a0cf4
--- /dev/null
+++ b/graphics/java/android/graphics/Mesh.java
@@ -0,0 +1,317 @@
+/*
+ * 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.graphics;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+
+/**
+ * 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,
+ * 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.
+ *
+ * @hide
+ */
+public class Mesh {
+    private long mNativeMeshWrapper;
+    private boolean mIsIndexed;
+
+    /**
+     * Enum to determine how the mesh is represented.
+     */
+    public enum Mode {Triangles, TriangleStrip}
+
+    private static class MeshHolder {
+        public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
+                NativeAllocationRegistry.createMalloced(
+                        MeshSpecification.class.getClassLoader(), nativeGetFinalizer());
+    }
+
+    /**
+     * Generates a {@link Mesh} object.
+     *
+     * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
+     * @param mode         {@link Mode} enum
+     * @param vertexBuffer vertex buffer representing through {@link Buffer}.
+     * @param vertexCount  the number of vertices represented in the vertexBuffer.
+     * @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,
+                vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
+                bounds.top, bounds.right, bounds.bottom);
+        if (nativeMesh == 0) {
+            throw new IllegalArgumentException("Mesh construction failed.");
+        }
+        return new Mesh(nativeMesh, false);
+    }
+
+    /**
+     * Generates an indexed {@link Mesh} object.
+     *
+     * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
+     * @param mode         {@link Mode} enum
+     * @param vertexBuffer vertex buffer representing through {@link Buffer}.
+     * @param vertexCount  the number of vertices represented in the vertexBuffer.
+     * @param indexBuffer  index buffer representing through {@link ShortBuffer}.
+     * @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,
+                vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
+                indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
+                bounds.top, bounds.right, bounds.bottom);
+        if (nativeMesh == 0) {
+            throw new IllegalArgumentException("Mesh construction failed.");
+        }
+        return new Mesh(nativeMesh, true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the color uniform declared in the shader program.
+     * @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) {
+        setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the color uniform declared in the shader program.
+     * @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) {
+        Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the color uniform declared in the shader program.
+     * @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) {
+        if (color == null) {
+            throw new NullPointerException("The color parameter must not be null");
+        }
+
+        Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @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) {
+        setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @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) {
+        setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @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.
+     * @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) {
+        setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @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.
+     * @param value3      third float value corresponding to the float uniform with the given name.
+     * @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) {
+        setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @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) {
+        setUniform(uniformName, values, false);
+    }
+
+    private void setFloatUniform(
+            String uniformName, float value1, float value2, float value3, float value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        nativeUpdateUniforms(
+                mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
+        nativeUpdateMesh(mNativeMeshWrapper);
+    }
+
+    private void setUniform(String uniformName, float[] values, boolean isColor) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+
+        nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values, isColor);
+        nativeUpdateMesh(mNativeMeshWrapper);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @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) {
+        setIntUniform(uniformName, value, 0, 0, 0, 1);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @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) {
+        setIntUniform(uniformName, value1, value2, 0, 0, 2);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @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.
+     * @param value3      third value corresponding to the int uniform with the given name.
+     */
+    public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+        setIntUniform(uniformName, value1, value2, value3, 0, 3);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @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.
+     * @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) {
+        setIntUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @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) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+        nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values);
+        nativeUpdateMesh(mNativeMeshWrapper);
+    }
+
+    private void setIntUniform(
+            String uniformName, int value1, int value2, int value3, int value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+
+        nativeUpdateUniforms(
+                mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
+        nativeUpdateMesh(mNativeMeshWrapper);
+    }
+
+    private Mesh(long nativeMeshWrapper, boolean isIndexed) {
+        mNativeMeshWrapper = nativeMeshWrapper;
+        this.mIsIndexed = isIndexed;
+        MeshHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(this, mNativeMeshWrapper);
+    }
+
+    private static native long nativeGetFinalizer();
+
+    private static native long nativeMake(long meshSpec, int mode, Buffer vertexBuffer,
+            boolean isDirect, int vertexCount, int vertexOffset, int left, int top, int right,
+            int bottom);
+
+    private static native long nativeMakeIndexed(long meshSpec, int mode, Buffer vertexBuffer,
+            boolean isVertexDirect, int vertexCount, int vertexOffset, ShortBuffer indexBuffer,
+            boolean isIndexDirect, int indexCount, int indexOffset, int left, int top, int right,
+            int bottom);
+
+    private static native void nativeUpdateUniforms(long builder, String uniformName, float value1,
+            float value2, float value3, float value4, int count);
+
+    private static native void nativeUpdateUniforms(
+            long builder, String uniformName, float[] values, boolean isColor);
+
+    private static native void nativeUpdateUniforms(long builder, String uniformName, int value1,
+            int value2, int value3, int value4, int count);
+
+    private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
+
+    private static native void nativeUpdateMesh(long nativeMeshWrapper);
+}
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
new file mode 100644
index 0000000..45c13af
--- /dev/null
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -0,0 +1,194 @@
+/*
+ * 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.graphics;
+
+import android.annotation.IntDef;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * 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 alpha type.
+ *
+ * Note that there are several limitations on various mesh specifications:
+ * 1. The max amount of attributes allowed is 8.
+ * 2. The offset alignment length is 4 bytes.
+ * 2. The max stride length is 1024.
+ * 3. The max amount of varyings is 6.
+ *
+ * These should be kept in mind when generating a mesh specification, as exceeding them will
+ * lead to errors.
+ *
+ * @hide
+ */
+public class MeshSpecification {
+    long mNativeMeshSpec;
+
+    /**
+     * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+     * to determine alpha type
+     */
+    @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
+    public @interface AlphaType {
+    }
+
+    public static final int UNKNOWN = 0;
+    public static final int OPAQUE = 1;
+    public static final int PREMUL = 2;
+    public static final int UNPREMULT = 3;
+
+    /**
+     * Constants for {@link Attribute} and {@link Varying} for determining the data type.
+     */
+    @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
+    public @interface Type {
+    }
+
+    public static final int FLOAT = 0;
+    public static final int FLOAT2 = 1;
+    public static final int FLOAT3 = 2;
+    public static final int FLOAT4 = 3;
+    public static final int UBYTE4 = 4;
+
+    /**
+     * Data class to represent a single attribute in a shader. Note that type parameter must be
+     * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     */
+    public static class Attribute {
+        @Type
+        private int mType;
+        private int mOffset;
+        private String mName;
+
+        public Attribute(@Type int type, int offset, String name) {
+            mType = type;
+            mOffset = offset;
+            mName = name;
+        }
+    }
+
+    /**
+     * Data class to represent a single varying variable. Note that type parameter must be
+     * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     */
+    public static class Varying {
+        @Type
+        private int mType;
+        private String mName;
+
+        public Varying(@Type int type, String name) {
+            mType = type;
+            mName = name;
+        }
+    }
+
+    private static class MeshSpecificationHolder {
+        public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
+                NativeAllocationRegistry.createMalloced(
+                        MeshSpecification.class.getClassLoader(), nativeGetFinalizer());
+    }
+
+    /**
+     * Creates a {@link MeshSpecification} object.
+     *
+     * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
+     *                       8.
+     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
+     * @param vertexShader   vertex shader to be supplied to the mesh.
+     * @param fragmentShader fragment shader to be suppied 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);
+        if (nativeMeshSpec == 0) {
+            throw new IllegalArgumentException("MeshSpecification construction failed");
+        }
+        return new MeshSpecification(nativeMeshSpec);
+    }
+
+    /**
+     * Creates a {@link MeshSpecification} object.
+     *
+     * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
+     *                       8.
+     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of
+     *                       6.
+     * @param vertexShader   vertex shader to be supplied to the mesh.
+     * @param fragmentShader fragment shader to be supplied to the mesh.
+     * @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,
+                fragmentShader, colorSpace.getNativeInstance());
+        if (nativeMeshSpec == 0) {
+            throw new IllegalArgumentException("MeshSpecification construction failed");
+        }
+        return new MeshSpecification(nativeMeshSpec);
+    }
+
+    /**
+     * Creates a {@link MeshSpecification} object.
+     *
+     * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
+     *                       8.
+     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
+     * @param vertexShader   vertex shader code to be supplied to the mesh.
+     * @param fragmentShader fragment shader code to be suppied 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.
+     * @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,
+            @AlphaType int alphaType) {
+        long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
+                fragmentShader, colorSpace.getNativeInstance(), alphaType);
+        if (nativeMeshSpec == 0) {
+            throw new IllegalArgumentException("MeshSpecification construction failed");
+        }
+        return new MeshSpecification(nativeMeshSpec);
+    }
+
+    private MeshSpecification(long meshSpec) {
+        mNativeMeshSpec = meshSpec;
+        MeshSpecificationHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(
+                this, meshSpec);
+    }
+
+    private static native long nativeGetFinalizer();
+
+    private static native long nativeMake(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader);
+
+    private static native long nativeMakeWithCS(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader, long colorSpace);
+
+    private static native long nativeMakeWithAlpha(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader, long colorSpace,
+            int alphaType);
+}
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 33b9a47..bfda690 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -281,7 +281,7 @@
             return mConicWeight;
         }
 
-        public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+        Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
             mVerb = verb;
             mPoints = points;
             mConicWeight = conicWeight;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3b7d0e1..9fb627f 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1207,6 +1207,7 @@
      * It is safe to call this method twice or more on the same instance.
      * @hide
      */
+    @TestApi
     public void releaseNativeObjectForTest() {
         mCleaner.run();
     }
@@ -1294,6 +1295,13 @@
     /**
      * Deserialize the font mapping from the serialized byte buffer.
      *
+     * <p>Warning: the given {@code buffer} must outlive generated Typeface
+     * objects in {@code out}. In production code, this is guaranteed by
+     * storing the buffer in {@link #sSystemFontMapBuffer}.
+     * If you call this method in a test, please make sure to destroy the
+     * generated Typeface objects by calling
+     * {@link #releaseNativeObjectForTest()}.
+     *
      * @hide
      */
     @TestApi
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 82ced43..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -382,9 +382,9 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Window}
+             * Creates a PixelCopy Builder for the given {@link Window}
              * @param source The Window to copy from
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull Window source) {
@@ -394,7 +394,7 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+             * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is
              * attached to.
              *
              * Note that this copy request is not cropped to the area the View occupies by default.
@@ -404,7 +404,7 @@
              *
              * @param source A View that {@link View#isAttachedToWindow() is attached} to a window
              *               that will be used to retrieve the window to copy from.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull View source) {
@@ -427,10 +427,10 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Surface}
+             * Creates a PixelCopy Builder for the given {@link Surface}
              *
              * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull Surface source) {
@@ -441,12 +441,12 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Surface} belonging to the
+             * Creates a PixelCopy Builder for the {@link Surface} belonging to the
              * given {@link SurfaceView}
              *
              * @param source The SurfaceView to copy from. The backing surface must be
              *               {@link Surface#isValid() valid}
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 6245598..8c42547 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -196,6 +196,7 @@
     @StringDef(prefix = { "KEY_" }, value = {
         KEY_ALGORITHM_RSA,
         KEY_ALGORITHM_EC,
+        KEY_ALGORITHM_XDH,
         KEY_ALGORITHM_AES,
         KEY_ALGORITHM_HMAC_SHA1,
         KEY_ALGORITHM_HMAC_SHA224,
@@ -211,6 +212,11 @@
     /** Elliptic Curve (EC) Cryptography key. */
     public static final String KEY_ALGORITHM_EC = "EC";
 
+    /** Curve 25519 based Agreement key.
+     * @hide
+     */
+    public static final String KEY_ALGORITHM_XDH = "XDH";
+
     /** Advanced Encryption Standard (AES) key. */
     public static final String KEY_ALGORITHM_AES = "AES";
 
@@ -246,7 +252,8 @@
 
         public static int toKeymasterAsymmetricKeyAlgorithm(
                 @NonNull @KeyAlgorithmEnum String algorithm) {
-            if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
+            if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)
+                    || KEY_ALGORITHM_XDH.equalsIgnoreCase(algorithm)) {
                 return KeymasterDefs.KM_ALGORITHM_EC;
             } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
                 return KeymasterDefs.KM_ALGORITHM_RSA;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
index 4e73bd9..4505eaf 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
@@ -24,13 +24,9 @@
 import android.system.keystore2.KeyDescriptor;
 import android.system.keystore2.KeyMetadata;
 
-import java.security.AlgorithmParameters;
-import java.security.NoSuchAlgorithmException;
 import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECGenParameterSpec;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
-import java.security.spec.InvalidParameterSpecException;
 
 /**
  * {@link ECPublicKey} backed by keystore.
@@ -62,34 +58,13 @@
         }
     }
 
-    private static String getEcCurveFromKeymaster(int ecCurve) {
-        switch (ecCurve) {
-            case android.hardware.security.keymint.EcCurve.P_224:
-                return "secp224r1";
-            case android.hardware.security.keymint.EcCurve.P_256:
-                return "secp256r1";
-            case android.hardware.security.keymint.EcCurve.P_384:
-                return "secp384r1";
-            case android.hardware.security.keymint.EcCurve.P_521:
-                return "secp521r1";
-        }
-        return "";
-    }
-
-    private ECParameterSpec getCurveSpec(String name)
-            throws NoSuchAlgorithmException, InvalidParameterSpecException {
-        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
-        parameters.init(new ECGenParameterSpec(name));
-        return parameters.getParameterSpec(ECParameterSpec.class);
-    }
-
     @Override
     public AndroidKeyStorePrivateKey getPrivateKey() {
         ECParameterSpec params = mParams;
         for (Authorization a : getAuthorizations()) {
             try {
                 if (a.keyParameter.tag == KeymasterDefs.KM_TAG_EC_CURVE) {
-                    params = getCurveSpec(getEcCurveFromKeymaster(
+                    params = KeymasterUtils.getCurveSpec(KeymasterUtils.getEcCurveFromKeymaster(
                             a.keyParameter.value.getEcCurve()));
                     break;
                 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
index 4caa47f..7292cd3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
@@ -32,7 +32,6 @@
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.interfaces.ECKey;
-import java.security.interfaces.XECKey;
 import java.security.spec.AlgorithmParameterSpec;
 import java.util.ArrayList;
 import java.util.List;
@@ -134,10 +133,15 @@
             throw new InvalidKeyException("key == null");
         } else if (!(key instanceof PublicKey)) {
             throw new InvalidKeyException("Only public keys supported. Key: " + key);
-        } else if (!(mKey instanceof ECKey && key instanceof ECKey)
-                && !(mKey instanceof XECKey && key instanceof XECKey)) {
+        } else if (mKey instanceof ECKey && !(key instanceof ECKey)
+                /*&& !(mKey instanceof XECKey && key instanceof XECKey)*/) {
+        /** TODO This condition is temporary modified, because OpenSSL implementation does not
+         * implement OpenSSLX25519PublicKey from XECKey interface (b/214203951).
+         * This change has to revert once conscrypt implements OpenSSLX25519PublicKey from
+         * XECKey interface.
+         */
             throw new InvalidKeyException(
-                    "Public and Private key should be of the same type:");
+                    "Public and Private key should be of the same type.");
         } else if (mKey instanceof ECKey
                 && !((ECKey) key).getParams().getCurve()
                 .equals(((ECKey) mKey).getParams().getCurve())) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 7a320ba..2d609e8 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.security.keymint.EcCurve;
 import android.hardware.security.keymint.HardwareAuthenticatorType;
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
@@ -67,6 +68,14 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.EdECKey;
+import java.security.interfaces.EdECPrivateKey;
+import java.security.interfaces.XECKey;
+import java.security.interfaces.XECPrivateKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.NamedParameterSpec;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -567,22 +576,14 @@
                         spec.getMaxUsageCount()
                 ));
             }
-            if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) {
-                if (key instanceof ECKey) {
-                    ECKey ecKey = (ECKey) key;
-                    importArgs.add(KeyStore2ParameterUtils.makeEnum(
-                            KeymasterDefs.KM_TAG_EC_CURVE,
-                            KeyProperties.EcCurve.toKeymasterCurve(ecKey.getParams())
-                    ));
-                }
+            if (KeymasterDefs.KM_ALGORITHM_EC
+                    == KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+                            key.getAlgorithm())) {
+                importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                        KeymasterDefs.KM_TAG_EC_CURVE,
+                        getKeymasterEcCurve(key)
+                ));
             }
-            /* TODO: check for Ed25519(EdDSA) or X25519(XDH) key algorithm and
-             *  add import args for KM_TAG_EC_CURVE as EcCurve.CURVE_25519.
-             *  Currently conscrypt does not support EdDSA key import and XDH keys are not an
-             *  instance of XECKey, hence these conditions are not added, once it is fully
-             *  implemented by conscrypt, we can add CURVE_25519 argument for EdDSA and XDH
-             *  algorithms.
-             */
         } catch (IllegalArgumentException | IllegalStateException e) {
             throw new KeyStoreException(e);
         }
@@ -608,6 +609,31 @@
         }
     }
 
+    private int getKeymasterEcCurve(PrivateKey key) {
+        if (key instanceof ECKey) {
+            ECParameterSpec param = ((ECPrivateKey) key).getParams();
+            int kmECCurve = KeymasterUtils.getKeymasterEcCurve(KeymasterUtils.getCurveName(param));
+            if (kmECCurve >= 0) {
+                return kmECCurve;
+            }
+        } else if (key instanceof XECKey) {
+            AlgorithmParameterSpec param = ((XECPrivateKey) key).getParams();
+            if (param.equals(NamedParameterSpec.X25519)) {
+                return EcCurve.CURVE_25519;
+            }
+        } else if (key.getAlgorithm().equals("XDH")) {
+            // TODO com.android.org.conscrypt.OpenSSLX25519PrivateKey does not implement XECKey,
+            //  this case is not required once it implements XECKey interface(b/214203951).
+            return EcCurve.CURVE_25519;
+        } else if (key instanceof EdECKey) {
+            AlgorithmParameterSpec param = ((EdECPrivateKey) key).getParams();
+            if (param.equals(NamedParameterSpec.ED25519)) {
+                return EcCurve.CURVE_25519;
+            }
+        }
+        throw new IllegalArgumentException("Unexpected Key " + key.getClass().getName());
+    }
+
     private static void assertCanReplace(String alias, @Domain int targetDomain,
             int targetNamespace, KeyDescriptor descriptor)
             throws KeyStoreException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java
index 9f3df3d..6913834 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java
@@ -88,7 +88,7 @@
                 getUserKeyDescriptor(),
                 getKeyIdDescriptor().nspace,
                 getAuthorizations(),
-                "x25519",
+                "XDH",
                 getSecurityLevel());
     }
 
diff --git a/keystore/java/android/security/keystore2/KeymasterUtils.java b/keystore/java/android/security/keystore2/KeymasterUtils.java
index de4696c..614e368 100644
--- a/keystore/java/android/security/keystore2/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore2/KeymasterUtils.java
@@ -20,7 +20,12 @@
 import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyProperties;
 
+import java.security.AlgorithmParameters;
+import java.security.NoSuchAlgorithmException;
 import java.security.ProviderException;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
 
 /**
  * @hide
@@ -121,4 +126,65 @@
                 break;
         }
     }
+
+    static String getEcCurveFromKeymaster(int ecCurve) {
+        switch (ecCurve) {
+            case android.hardware.security.keymint.EcCurve.P_224:
+                return "secp224r1";
+            case android.hardware.security.keymint.EcCurve.P_256:
+                return "secp256r1";
+            case android.hardware.security.keymint.EcCurve.P_384:
+                return "secp384r1";
+            case android.hardware.security.keymint.EcCurve.P_521:
+                return "secp521r1";
+        }
+        return "";
+    }
+
+    static int getKeymasterEcCurve(String ecCurveName) {
+        if (ecCurveName.equals("secp224r1")) {
+            return android.hardware.security.keymint.EcCurve.P_224;
+        } else if (ecCurveName.equals("secp256r1")) {
+            return android.hardware.security.keymint.EcCurve.P_256;
+        } else if (ecCurveName.equals("secp384r1")) {
+            return android.hardware.security.keymint.EcCurve.P_384;
+        } else if (ecCurveName.equals("secp521r1")) {
+            return android.hardware.security.keymint.EcCurve.P_521;
+        }
+        return -1;
+    }
+
+    static ECParameterSpec getCurveSpec(String name)
+            throws NoSuchAlgorithmException, InvalidParameterSpecException {
+        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
+        parameters.init(new ECGenParameterSpec(name));
+        return parameters.getParameterSpec(ECParameterSpec.class);
+    }
+
+    static String getCurveName(ECParameterSpec spec) {
+        if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp224r1")) {
+            return "secp224r1";
+        } else if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp256r1")) {
+            return "secp256r1";
+        } else if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp384r1")) {
+            return "secp384r1";
+        } else if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp521r1")) {
+            return "secp521r1";
+        }
+        return null;
+    }
+
+    private static boolean isECParameterSpecOfCurve(ECParameterSpec spec, String curveName) {
+        try {
+            ECParameterSpec curveSpec = KeymasterUtils.getCurveSpec(curveName);
+            if (curveSpec.getCurve().equals(spec.getCurve())
+                    && curveSpec.getOrder().equals(spec.getOrder())
+                    && curveSpec.getGenerator().equals(spec.getGenerator())) {
+                return true;
+            }
+        } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
+            return false;
+        }
+        return false;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a0dde6a..2ec9e8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.animation;
 
+import android.graphics.Path;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
@@ -53,6 +54,11 @@
     public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
 
     /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
      * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
      * is disappearing e.g. when moving off screen.
      */
@@ -81,4 +87,14 @@
 
     public static final PathInterpolator DIM_INTERPOLATOR =
             new PathInterpolator(.23f, .87f, .52f, -0.11f);
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
new file mode 100644
index 0000000..36cf29a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background surface for the back animations
+ */
+public class BackAnimationBackground {
+    private static final int BACKGROUND_LAYER = -1;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private SurfaceControl mBackgroundSurface;
+
+    public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+    }
+
+    void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundSurface != null) {
+            return;
+        }
+
+        final float[] colorComponents = new float[] { Color.red(color) / 255.f,
+                Color.green(color) / 255.f, Color.blue(color) / 255.f };
+
+        final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+                .setName("back-animation-background")
+                .setCallsite("BackAnimationBackground")
+                .setColorLayer();
+
+        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+        mBackgroundSurface = colorLayerBuilder.build();
+        transaction.setColor(mBackgroundSurface, colorComponents)
+                .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
+                .show(mBackgroundSurface);
+    }
+
+    void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundSurface == null) {
+            return;
+        }
+
+        if (mBackgroundSurface.isValid()) {
+            transaction.remove(mBackgroundSurface);
+        }
+        mBackgroundSurface = null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f811940..0133f6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -138,14 +138,18 @@
         }
     };
 
+    private final BackAnimationBackground mAnimationBackground;
+
     public BackAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
-            Context context) {
+            Context context,
+            @NonNull BackAnimationBackground backAnimationBackground) {
         this(shellInit, shellController, shellExecutor, backgroundHandler,
-                ActivityTaskManager.getService(), context, context.getContentResolver());
+                ActivityTaskManager.getService(), context, context.getContentResolver(),
+                backAnimationBackground);
     }
 
     @VisibleForTesting
@@ -155,7 +159,8 @@
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull IActivityTaskManager activityTaskManager,
-            Context context, ContentResolver contentResolver) {
+            Context context, ContentResolver contentResolver,
+            @NonNull BackAnimationBackground backAnimationBackground) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -163,6 +168,7 @@
         mContentResolver = contentResolver;
         mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
+        mAnimationBackground = backAnimationBackground;
     }
 
     @VisibleForTesting
@@ -184,10 +190,14 @@
             return;
         }
 
-        final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+        final CrossTaskBackAnimation crossTaskAnimation =
+                new CrossTaskBackAnimation(mContext, mAnimationBackground);
         mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
-                new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
-        // TODO (238474994): register cross activity animation when it's completed.
+                crossTaskAnimation.mBackAnimationRunner);
+        final CrossActivityAnimation crossActivityAnimation =
+                new CrossActivityAnimation(mContext, mAnimationBackground);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                crossActivityAnimation.mBackAnimationRunner);
         // TODO (236760237): register dialog close animation when it's completed.
     }
 
@@ -275,7 +285,8 @@
         @Override
         public void clearBackToLauncherCallback() {
             executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
-                    (controller) -> controller.clearBackToLauncherCallback());
+                    (controller) -> controller.unregisterAnimation(
+                            BackNavigationInfo.TYPE_RETURN_TO_HOME));
         }
 
         @Override
@@ -289,8 +300,8 @@
         mAnimationDefinition.set(type, runner);
     }
 
-    private void clearBackToLauncherCallback() {
-        mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+    void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+        mAnimationDefinition.remove(type);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
new file mode 100644
index 0000000..9f6bc5d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityAnimation {
+    /**
+     * Minimum scale of the entering/closing window.
+     */
+    private static final float MIN_WINDOW_SCALE = 0.9f;
+
+    /**
+     * Minimum alpha of the closing/entering window.
+     */
+    private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
+
+    /**
+     * Progress value to fly out closing window and fly in entering window.
+     */
+    private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
+
+    /** Max window translation in the Y axis. */
+    private static final int WINDOW_MAX_DELTA_Y = 160;
+
+    /** Duration of fade in/out entering window. */
+    private static final int FADE_IN_DURATION = 100;
+    /** Duration of post animation after gesture committed. */
+    private static final int POST_ANIMATION_DURATION = 350;
+    private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+
+    private final Rect mStartTaskRect = new Rect();
+    private final float mCornerRadius;
+
+    // The closing window properties.
+    private final RectF mClosingRect = new RectF();
+
+    // The entering window properties.
+    private final Rect mEnteringStartRect = new Rect();
+    private final RectF mEnteringRect = new RectF();
+
+    private float mCurrentAlpha = 1.0f;
+
+    private float mEnteringMargin = 0;
+    private ValueAnimator mEnteringAnimator;
+    private boolean mEnteringWindowShow = false;
+
+    private final PointF mInitialTouchPos = new PointF();
+
+    private final Matrix mTransformMatrix = new Matrix();
+
+    private final float[] mTmpFloat9 = new float[9];
+
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private boolean mBackInProgress = false;
+
+    private PointF mTouchPos = new PointF();
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    final BackAnimationRunner mBackAnimationRunner;
+
+    private final BackAnimationBackground mBackground;
+
+    CrossActivityAnimation(Context context, BackAnimationBackground background) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackground = background;
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+
+    private float getInterpolatedProgress(float backProgress) {
+        return INTERPOLATOR.getInterpolation(backProgress);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+        mTransaction.setAnimationTransaction();
+
+        // Offset start rectangle to align task bounds.
+        mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+        mStartTaskRect.offsetTo(0, 0);
+
+        // Draw background with task background color.
+        mBackground.ensureBackground(
+                mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+    }
+
+    private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+        final float scale = targetRect.width() / mStartTaskRect.width();
+        mTransformMatrix.reset();
+        mTransformMatrix.setScale(scale, scale);
+        mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+        mTransaction.setAlpha(leash, targetAlpha)
+                .setMatrix(leash, mTransformMatrix, mTmpFloat9)
+                .setWindowCrop(leash, mStartTaskRect)
+                .setCornerRadius(leash, mCornerRadius);
+    }
+
+    private void finishAnimation() {
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+        if (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
+        }
+
+        mTransaction.apply();
+        mBackInProgress = false;
+        mTransformMatrix.reset();
+        mInitialTouchPos.set(0, 0);
+        mEnteringWindowShow = false;
+        mEnteringMargin = 0;
+
+        if (mFinishCallback != null) {
+            try {
+                mFinishCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            mFinishCallback = null;
+        }
+    }
+
+    private void onGestureProgress(@NonNull BackEvent backEvent) {
+        if (!mBackInProgress) {
+            mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+            mBackInProgress = true;
+        }
+        mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            return;
+        }
+
+        final float progress = getInterpolatedProgress(backEvent.getProgress());
+        final float touchY = mTouchPos.y;
+
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
+
+        final float closingWidth = closingScale * width;
+        final float closingHeight = (float) height / width * closingWidth;
+
+        // Move the window along the X axis.
+        final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+
+        // Move the window along the Y axis.
+        final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+        final float closingTop = (height - closingHeight) * 0.5f + deltaY;
+        mClosingRect.set(
+                closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+        mEnteringRect.set(mClosingRect);
+
+        // Switch closing/entering targets while reach to the threshold progress.
+        if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
+            return;
+        }
+
+        // Present windows and update the alpha.
+        mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
+        mClosingRect.offset(mEnteringMargin, 0);
+        mEnteringRect.offset(mEnteringMargin - width, 0);
+
+        applyTransform(
+                mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
+        applyTransform(
+                mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
+        mTransaction.apply();
+    }
+
+    private boolean showEnteringWindow(boolean show) {
+        if (mEnteringAnimator == null) {
+            mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
+            mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
+            mEnteringAnimator.addUpdateListener(animation -> {
+                float progress = animation.getAnimatedFraction();
+                final int width = mStartTaskRect.width();
+                mEnteringMargin = width * progress;
+                // We don't animate to 0 or the surface would become invisible and lose focus.
+                final float alpha = progress >= 0.5f ? 0.01f
+                        : mapRange(progress * 2, mCurrentAlpha, 0.01f);
+                mClosingRect.offset(mEnteringMargin, 0);
+                mEnteringRect.offset(mEnteringMargin - width, 0);
+
+                applyTransform(mClosingTarget.leash, mClosingRect, alpha);
+                applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
+                mTransaction.apply();
+            });
+        }
+
+        if (mEnteringAnimator.isRunning()) {
+            return true;
+        }
+
+        if (mEnteringWindowShow == show) {
+            return false;
+        }
+
+        mEnteringWindowShow = show;
+        if (show) {
+            mEnteringAnimator.start();
+        } else {
+            mEnteringAnimator.reverse();
+        }
+        return true;
+    }
+
+    private void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            finishAnimation();
+            return;
+        }
+
+        // End the fade in animation.
+        if (mEnteringAnimator.isRunning()) {
+            mEnteringAnimator.cancel();
+        }
+
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase.
+        mEnteringRect.round(mEnteringStartRect);
+        mTransaction.hide(mClosingTarget.leash);
+
+        ValueAnimator valueAnimator =
+                ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
+        valueAnimator.setInterpolator(new DecelerateInterpolator());
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            updatePostCommitEnteringAnimation(progress);
+            mTransaction.apply();
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    private void updatePostCommitEnteringAnimation(float progress) {
+        float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+        float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+        float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+        float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+        float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+
+        mEnteringRect.set(left, top, left + width, top + height);
+        applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
+    }
+
+    private final class Callback extends IOnBackInvokedCallback.Default {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossActivityAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            // End the fade in animation.
+            if (mEnteringAnimator.isRunning()) {
+                mEnteringAnimator.cancel();
+            }
+            // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    }
+
+    private final class Runner extends IRemoteAnimationRunner.Default {
+        @Override
+        public void onAnimationStart(
+                int transit,
+                RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+
+        @Override
+        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            finishAnimation();
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 2074b6a..a9a7b77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -61,7 +61,7 @@
  */
 @ShellMainThread
 class CrossTaskBackAnimation {
-    private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+    private static final int BACKGROUNDCOLOR = 0x43433A;
 
     /**
      * Minimum scale of the entering window.
@@ -106,7 +106,6 @@
 
     private RemoteAnimationTarget mEnteringTarget;
     private RemoteAnimationTarget mClosingTarget;
-    private SurfaceControl mBackgroundSurface;
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
     private boolean mBackInProgress = false;
@@ -115,56 +114,15 @@
     private float mProgress = 0;
     private PointF mTouchPos = new PointF();
     private IRemoteAnimationFinishedCallback mFinishCallback;
-
     private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    final BackAnimationRunner mBackAnimationRunner;
 
-    final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
-        @Override
-        public void onBackStarted(BackEvent backEvent) {
-            mProgressAnimator.onBackStarted(backEvent,
-                    CrossTaskBackAnimation.this::onGestureProgress);
-        }
+    private final BackAnimationBackground mBackground;
 
-        @Override
-        public void onBackProgressed(@NonNull BackEvent backEvent) {
-            mProgressAnimator.onBackProgressed(backEvent);
-        }
-
-        @Override
-        public void onBackCancelled() {
-            mProgressAnimator.reset();
-            finishAnimation();
-        }
-
-        @Override
-        public void onBackInvoked() {
-            mProgressAnimator.reset();
-            onGestureCommitted();
-        }
-    };
-
-    final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
-        @Override
-        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                IRemoteAnimationFinishedCallback finishedCallback) {
-            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
-            for (RemoteAnimationTarget a : apps) {
-                if (a.mode == MODE_CLOSING) {
-                    mClosingTarget = a;
-                }
-                if (a.mode == MODE_OPENING) {
-                    mEnteringTarget = a;
-                }
-            }
-
-            startBackAnimation();
-            mFinishCallback = finishedCallback;
-        }
-    };
-
-    CrossTaskBackAnimation(Context context) {
+    CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackground = background;
     }
 
     private float getInterpolatedProgress(float backProgress) {
@@ -182,14 +140,7 @@
         mStartTaskRect.offsetTo(0, 0);
 
         // Draw background.
-        mBackgroundSurface = new SurfaceControl.Builder()
-                .setName("Background of Back Navigation")
-                .setColorLayer()
-                .setHidden(false)
-                .build();
-        mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
-                .setLayer(mBackgroundSurface, -1);
-        mTransaction.apply();
+        mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
     }
 
     private void updateGestureBackProgress(float progress, BackEvent event) {
@@ -300,11 +251,11 @@
             mClosingTarget = null;
         }
 
-        if (mBackgroundSurface != null) {
-            mBackgroundSurface.release();
-            mBackgroundSurface = null;
+        if (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
         }
 
+        mTransaction.apply();
         mBackInProgress = false;
         mTransformMatrix.reset();
         mClosingCurrentRect.setEmpty();
@@ -362,4 +313,49 @@
     private static float mapRange(float value, float min, float max) {
         return min + (value * (max - min));
     }
+
+    private final class Callback extends IOnBackInvokedCallback.Default  {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossTaskBackAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    };
+
+    private final class Runner extends IRemoteAnimationRunner.Default {
+        @Override
+        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+    };
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 962be9d..4ea8a5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationBackground;
 import com.android.wm.shell.back.BackAnimationController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -93,13 +94,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -255,21 +256,30 @@
 
     @WMSingleton
     @Provides
+    static BackAnimationBackground provideBackAnimationBackground(
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        return new BackAnimationBackground(rootTaskDisplayAreaOrganizer);
+    }
+
+    @WMSingleton
+    @Provides
     static Optional<BackAnimationController> provideBackAnimationController(
             Context context,
             ShellInit shellInit,
             ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
-            @ShellBackgroundThread Handler backgroundHandler
+            @ShellBackgroundThread Handler backgroundHandler,
+            BackAnimationBackground backAnimationBackground
     ) {
         if (BackAnimationController.IS_ENABLED) {
             return Optional.of(
                     new BackAnimationController(shellInit, shellController, shellExecutor,
-                            backgroundHandler, context));
+                            backgroundHandler, context, backAnimationBackground));
         }
         return Optional.empty();
     }
 
+
     //
     // Bubbles (optional feature)
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..8ba2583 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -179,8 +180,10 @@
                 // This is necessary in case there was a resize animation ongoing when exit PIP
                 // started, in which case the first resize will be skipped to let the exit
                 // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResize(tx, destinationBounds, direction, animationType);
-                sendOnPipTransitionFinished(direction);
+                finishResizeDelayedIfNeeded(() -> {
+                    finishResize(tx, destinationBounds, direction, animationType);
+                    sendOnPipTransitionFinished(direction);
+                });
             }
         }
 
@@ -196,6 +199,39 @@
         }
     };
 
+    /**
+     * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+     *
+     * This is done to avoid a race condition between the last transaction applied in
+     * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+     * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+     * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+     * the WCT should be the last transaction to finish the animation. However, it  may happen that
+     * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+     * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+     * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+     * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+     * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+     *
+     * To avoid this, we delay the finishResize operation until
+     * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+     */
+    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+        if (!shouldSyncPipTransactionWithMenu()) {
+            finishResizeRunnable.run();
+            return;
+        }
+
+        // Delay the finishResize to the next frame
+        Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+            mMainExecutor.execute(finishResizeRunnable);
+        }, null);
+    }
+
+    private boolean shouldSyncPipTransactionWithMenu() {
+        return mPipMenuController.isMenuVisible();
+    }
+
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, Rect destinationBounds) {
-                    if (mPipMenuController.isMenuVisible()) {
+                    if (shouldSyncPipTransactionWithMenu()) {
                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
                         return true;
                     }
@@ -1223,7 +1259,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
@@ -1265,7 +1301,7 @@
         mSurfaceTransactionHelper
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
                 if (mRemote.asBinder() != null) {
                     mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
             }
@@ -90,7 +90,13 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+                    startTransaction, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -124,7 +130,13 @@
             }
         };
         try {
-            mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT =
+                    RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().mergeAnimation(
+                    transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error merging remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -120,10 +121,10 @@
             public void onTransitionFinished(WindowContainerTransaction wct,
                     SurfaceControl.Transaction sct) {
                 unhandleDeath(remote.asBinder(), finishCallback);
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     mRequestedRemotes.remove(transition);
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
@@ -131,8 +132,14 @@
         };
         Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
         try {
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT =
+                    copyIfLocal(startTransaction, remote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
             handleDeath(remote.asBinder(), finishCallback);
-            remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -145,6 +152,28 @@
         return true;
     }
 
+    static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+            IRemoteTransition remote) {
+        // We care more about parceling than local (though they should be the same); so, use
+        // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+        if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+            // No local interface, so binder itself will parcel and thus we don't need to.
+            return t;
+        }
+        // Binder won't be parceling; however, the remotes assume they have their own native
+        // objects (and don't know if caller is local or not), so we need to make a COPY here so
+        // that the remote can clean it up without clearing the original transaction.
+        // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+        final Parcel p = Parcel.obtain();
+        try {
+            t.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
             }
         };
         try {
-            remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index e338221..6af81f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -322,6 +322,7 @@
                         .setPixelFormat(PixelFormat.RGBA_8888)
                         .setChildrenOnly(true)
                         .setAllowProtected(true)
+                        .setCaptureSecureLayers(true)
                         .build();
         final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
                 ScreenCapture.captureLayers(captureArgs);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 56d51bd..c6935c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -503,6 +503,7 @@
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
             onAbort(transitionToken);
+            releaseSurfaces(info);
             return;
         }
 
@@ -607,6 +608,15 @@
         onFinish(transition, wct, wctCB, false /* abort */);
     }
 
+    /**
+     * Releases an info's animation-surfaces. These don't need to persist and we need to release
+     * them asap so that SF can free memory sooner.
+     */
+    private void releaseSurfaces(@Nullable TransitionInfo info) {
+        if (info == null) return;
+        info.releaseAnimSurfaces();
+    }
+
     private void onFinish(IBinder transition,
             @Nullable WindowContainerTransaction wct,
             @Nullable WindowContainerTransactionCallback wctCB,
@@ -645,6 +655,11 @@
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+        if (active.mStartT != null) {
+            // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+            // is used later to disambiguate malformed transitions.
+            active.mStartT.close();
+        }
         // Merge all relevant transactions together
         SurfaceControl.Transaction fullFinish = active.mFinishT;
         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -664,12 +679,14 @@
             fullFinish.apply();
         }
         // Now perform all the finishes.
+        releaseSurfaces(active.mInfo);
         mActiveTransitions.remove(activeIdx);
         mOrganizer.finishTransition(transition, wct, wctCB);
         while (activeIdx < mActiveTransitions.size()) {
             if (!mActiveTransitions.get(activeIdx).mMerged) break;
             ActiveTransition merged = mActiveTransitions.remove(activeIdx);
             mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+            releaseSurfaces(merged.mInfo);
         }
         // sift through aborted transitions
         while (mActiveTransitions.size() > activeIdx
@@ -682,8 +699,9 @@
             }
             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
             for (int i = 0; i < mObservers.size(); ++i) {
-                mObservers.get(i).onTransitionFinished(active.mToken, true);
+                mObservers.get(i).onTransitionFinished(aborted.mToken, true);
             }
+            releaseSurfaces(aborted.mInfo);
         }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index d75c36c..bee9a90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -110,6 +110,9 @@
     @Mock
     private ShellController mShellController;
 
+    @Mock
+    private BackAnimationBackground mAnimationBackground;
+
     private BackAnimationController mController;
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
@@ -127,7 +130,7 @@
         mController = new BackAnimationController(mShellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver);
+                mContentResolver, mAnimationBackground);
         mController.setEnableUAnimation(true);
         mShellInit.init();
         mShellExecutor.flushAll();
@@ -239,7 +242,7 @@
         mController = new BackAnimationController(shellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver);
+                mContentResolver, mAnimationBackground);
         shellInit.init();
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
@@ -354,6 +357,10 @@
                 BackNavigationInfo.TYPE_DIALOG_CLOSE};
 
         for (int type: testTypes) {
+            unregisterAnimation(type);
+        }
+
+        for (int type: testTypes) {
             final ResultListener result = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
@@ -431,6 +438,10 @@
                 new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
     }
 
+    private void unregisterAnimation(int type) {
+        mController.unregisterAnimation(type);
+    }
+
     private static class ResultListener implements RemoteCallback.OnResultListener {
         boolean mBackNavigationDone = false;
         boolean mTriggerBack = false;
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
old mode 100755
new mode 100644
index 9aa3787..15aaae2
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,6 +18,7 @@
 
 #include "android-base/errors.h"
 #include "android-base/logging.h"
+#include "android-base/utf8.h"
 
 namespace android {
 
@@ -83,15 +84,16 @@
     return {};
   }
 
+  std::string overlay_path(loaded_idmap->OverlayApkPath());
+  auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
   std::unique_ptr<AssetsProvider> overlay_assets;
-  const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  if (IsFabricatedOverlay(overlay_path)) {
+  if (IsFabricatedOverlay(fd)) {
     // Fabricated overlays do not contain resource definitions. All of the overlay resource values
     // are defined inline in the idmap.
-    overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+    overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
   } else {
     // The overlay should be an APK.
-    overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+    overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd));
   }
   if (overlay_assets == nullptr) {
     return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3fa369d..cc7e871 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -22,6 +22,7 @@
 #include <iterator>
 #include <map>
 #include <set>
+#include <span>
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
@@ -111,7 +112,7 @@
   // A mapping from path of apk assets that could be target packages of overlays to the runtime
   // package id of its first loaded package. Overlays currently can only override resources in the
   // first package in the target resource table.
-  std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+  std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
 
   // Overlay resources are not directly referenced by an application so their resource ids
   // can change throughout the application's lifetime. Assign overlay package ids last.
@@ -134,7 +135,7 @@
     if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
       // The target package must precede the overlay package in the apk assets paths in order
       // to take effect.
-      auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
       if (iter == target_assets_package_ids.end()) {
          LOG(INFO) << "failed to find target package for overlay "
                    << loaded_idmap->OverlayApkPath();
@@ -179,7 +180,7 @@
         if (overlay_ref_table != nullptr) {
           // If this package is from an overlay, use a dynamic reference table that can rewrite
           // overlay resource ids to their corresponding target resource ids.
-          new_group.dynamic_ref_table = overlay_ref_table;
+          new_group.dynamic_ref_table = std::move(overlay_ref_table);
         }
 
         DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -187,9 +188,9 @@
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
 
-      // Add the package and to the set of packages with the same ID.
+      // Add the package to the set of packages with the same ID.
       PackageGroup* package_group = &package_groups_[idx];
-      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+      package_group->packages_.emplace_back().loaded_package_ = package.get();
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
       // Add the package name -> build time ID mappings.
@@ -201,29 +202,38 @@
 
       if (auto apk_assets_path = apk_assets->GetPath()) {
         // Overlay target ApkAssets must have been created using path based load apis.
-        target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+        target_assets_package_ids.emplace(*apk_assets_path, package_id);
       }
     }
   }
 
   // Now assign the runtime IDs so that we have a build-time to runtime ID map.
-  const auto package_groups_end = package_groups_.end();
-  for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
-    const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
-    for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
-      iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
-                                           iter->dynamic_ref_table->mAssignedPackageId);
-
-      // Add the alias resources to the dynamic reference table of every package group. Since
-      // staging aliases can only be defined by the framework package (which is not a shared
-      // library), the compile-time package id of the framework is the same across all packages
-      // that compile against the framework.
-      for (const auto& package : iter->packages_) {
-        for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
-          iter2->dynamic_ref_table->addAlias(entry.first, entry.second);
-        }
-      }
+  DynamicRefTable::AliasMap aliases;
+  for (const auto& group : package_groups_) {
+    const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName();
+    const auto name_16 = String16(package_name.c_str(), package_name.size());
+    for (auto&& inner_group : package_groups_) {
+      inner_group.dynamic_ref_table->addMapping(name_16,
+                                                group.dynamic_ref_table->mAssignedPackageId);
     }
+
+    for (const auto& package : group.packages_) {
+      const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
+      aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
+    }
+  }
+
+  if (!aliases.empty()) {
+    std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
+    // Add the alias resources to the dynamic reference table of every package group. Since
+    // staging aliases can only be defined by the framework package (which is not a shared
+    // library), the compile-time package id of the framework is the same across all packages
+    // that compile against the framework.
+    for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) {
+      group.dynamic_ref_table->setAliases(aliases);
+    }
+    package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases));
   }
 }
 
@@ -317,7 +327,7 @@
   return &loaded_package->GetOverlayableMap();
 }
 
-bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name,
+bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
                                             std::string* out) const {
   uint8_t package_id = 0U;
   for (const auto& apk_assets : apk_assets_) {
@@ -364,7 +374,7 @@
         const std::string name = ToFormattedResourceString(*res_name);
         output.append(base::StringPrintf(
             "resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
-            name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+            name.c_str(), info->name.data(), info->actor.data(), info->policy_flags));
       }
     }
   }
@@ -492,7 +502,7 @@
       continue;
     }
 
-    auto func = [&](const StringPiece& name, FileType type) {
+    auto func = [&](StringPiece name, FileType type) {
       AssetDir::FileInfo info;
       info.setFileName(String8(name.data(), name.size()));
       info.setFileType(type);
@@ -1271,7 +1281,7 @@
   return result;
 }
 
-static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
+static bool Utf8ToUtf16(StringPiece str, std::u16string* out) {
   ssize_t len =
       utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false);
   if (len < 0) {
@@ -1346,22 +1356,22 @@
 
 void AssetManager2::RebuildFilterList() {
   for (PackageGroup& group : package_groups_) {
-    for (ConfiguredPackage& impl : group.packages_) {
-      // Destroy it.
-      impl.filtered_configs_.~ByteBucketArray();
-
-      // Re-create it.
-      new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
-
+    for (ConfiguredPackage& package : group.packages_) {
+      package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); });
       // Create the filters here.
-      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
-        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1);
+      package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+        FilteredConfigGroup* group = nullptr;
         for (const auto& type_entry : type_spec.type_entries) {
           if (type_entry.config.match(configuration_)) {
-            group.type_entries.push_back(&type_entry);
+            if (!group) {
+              group = &package.filtered_configs_.editItemAt(type_id - 1);
+            }
+            group->type_entries.push_back(&type_entry);
           }
         }
       });
+      package.filtered_configs_.trimBuckets(
+          [](const auto& fcg) { return fcg.type_entries.empty(); });
     }
   }
 }
@@ -1402,30 +1412,34 @@
 std::unique_ptr<Theme> AssetManager2::NewTheme() {
   constexpr size_t kInitialReserveSize = 32;
   auto theme = std::unique_ptr<Theme>(new Theme(this));
+  theme->keys_.reserve(kInitialReserveSize);
   theme->entries_.reserve(kInitialReserveSize);
   return theme;
 }
 
+void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+                                   package_property_t excluded_property_flags) const {
+  for (const PackageGroup& package_group : package_groups_) {
+    const auto loaded_package = package_group.packages_.front().loaded_package_;
+    if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
+        && !func(loaded_package->GetPackageName(),
+                 package_group.dynamic_ref_table->mAssignedPackageId)) {
+      return;
+    }
+  }
+}
+
 Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
 }
 
 Theme::~Theme() = default;
 
 struct Theme::Entry {
-  uint32_t attr_res_id;
   ApkAssetsCookie cookie;
   uint32_t type_spec_flags;
   Res_value value;
 };
 
-namespace {
-struct ThemeEntryKeyComparer {
-  bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
-    return entry.attr_res_id < attr_res_id;
-  }
-};
-} // namespace
-
 base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
   ATRACE_NAME("Theme::ApplyStyle");
 
@@ -1454,18 +1468,20 @@
       continue;
     }
 
-    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
-                                     ThemeEntryKeyComparer{});
-    if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
+    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+    if (key_it != keys_.end() && *key_it == attr_res_id) {
       if (is_undefined) {
         // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
-        /// true.
+        // true.
+        keys_.erase(key_it);
         entries_.erase(entry_it);
       } else if (force) {
-        *entry_it = Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
+        *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
       }
     } else {
-      entries_.insert(entry_it, Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value});
+      keys_.insert(key_it, attr_res_id);
+      entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
     }
   }
   return {};
@@ -1476,6 +1492,7 @@
   ATRACE_NAME("Theme::Rebase");
   // Reset the entries without changing the vector capacity to prevent reallocations during
   // ApplyStyle.
+  keys_.clear();
   entries_.clear();
   asset_manager_ = am;
   for (size_t i = 0; i < style_count; i++) {
@@ -1484,16 +1501,14 @@
 }
 
 std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
-
   constexpr const uint32_t kMaxIterations = 20;
   uint32_t type_spec_flags = 0u;
   for (uint32_t i = 0; i <= kMaxIterations; i++) {
-    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
-                                     ThemeEntryKeyComparer{});
-    if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid);
+    if (key_it == keys_.end() || *key_it != resid) {
       return std::nullopt;
     }
-
+    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
     type_spec_flags |= entry_it->type_spec_flags;
     if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
       resid = entry_it->value.data;
@@ -1527,6 +1542,7 @@
 }
 
 void Theme::Clear() {
+  keys_.clear();
   entries_.clear();
 }
 
@@ -1538,11 +1554,12 @@
   type_spec_flags_ = source.type_spec_flags_;
 
   if (asset_manager_ == source.asset_manager_) {
+    keys_ = source.keys_;
     entries_ = source.entries_;
   } else {
-    std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
-    typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
-    std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
+    std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
+    using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
+    std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
 
     // Determine which ApkAssets are loaded in both theme AssetManagers.
     const auto src_assets = source.asset_manager_->GetApkAssets();
@@ -1570,15 +1587,17 @@
         }
 
         src_to_dest_asset_cookies.insert(std::make_pair(i, j));
-        src_asset_cookie_id_map.insert(std::make_pair(i, package_map));
+        src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map)));
         break;
       }
     }
 
     // Reset the data in the destination theme.
+    keys_.clear();
     entries_.clear();
 
-    for (const auto& entry : source.entries_) {
+    for (size_t i = 0, size = source.entries_.size(); i != size; ++i) {
+      const auto& entry = source.entries_[i];
       bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
                            || entry.value.dataType == Res_value::TYPE_REFERENCE
                            || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
@@ -1618,13 +1637,15 @@
         }
       }
 
+      const auto source_res_id = source.keys_[i];
+
       // The package id of the attribute needs to be rewritten to the package id of the
       // attribute in the destination.
-      int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+      int attribute_dest_package_id = get_package_id(source_res_id);
       if (attribute_dest_package_id != 0x01) {
         // Find the cookie of the attribute resource id in the source AssetManager
         base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
-            source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+            source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ ,
                                              true /* stop_at_first_match */,
                                              true /* ignore_configuration */);
         if (UNLIKELY(IsIOError(attribute_entry_result))) {
@@ -1648,16 +1669,15 @@
         attribute_dest_package_id = attribute_dest_package->second;
       }
 
-      auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
-                                     get_entry_id(entry.attr_res_id));
-      Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
-                                            Res_value{.dataType = entry.value.dataType,
-                                                      .data = attribute_data}};
-
+      auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id),
+                                     get_entry_id(source_res_id));
+      const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id);
+      const auto entry_it = entries_.begin() + (key_it - keys_.begin());
       // Since the entries were cleared, the attribute resource id has yet been mapped to any value.
-      auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
-                                       ThemeEntryKeyComparer{});
-      entries_.insert(entry_it, new_entry);
+      keys_.insert(key_it, dest_attr_id);
+      entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags,
+                                      Res_value{.dataType = entry.value.dataType,
+                                                .data = attribute_data}});
     }
   }
   return {};
@@ -1665,9 +1685,11 @@
 
 void Theme::Dump() const {
   LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
-  for (auto& entry : entries_) {
+  for (size_t i = 0, size = keys_.size(); i != size; ++i) {
+    auto res_id = keys_[i];
+    const auto& entry = entries_[i];
     LOG(INFO) << base::StringPrintf("  entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
-                                    entry.attr_res_id, entry.value.data, entry.value.dataType,
+                                    res_id, entry.value.data, entry.value.dataType,
                                     entry.cookie);
   }
 }
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index bce34d3..b9264c5 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -92,21 +92,27 @@
       last_mod_time_(last_mod_time) {}
 
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
-                                                             package_property_t flags) {
+                                                             package_property_t flags,
+                                                             base::unique_fd fd) {
+  const auto released_fd = fd.ok() ? fd.release() : -1;
   ZipArchiveHandle handle;
-  if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+  if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle)
+                                       : OpenArchiveFd(released_fd, path.c_str(), &handle)) {
     LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
     CloseArchive(handle);
     return {};
   }
 
   struct stat sb{.st_mtime = -1};
-  if (stat(path.c_str(), &sb) < 0) {
-    // Stat requires execute permissions on all directories path to the file. If the process does
-    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
-    // always have to return true.
-    LOG(WARNING) << "Failed to stat file '" << path << "': "
-                 << base::SystemErrorCodeToString(errno);
+  // Skip all up-to-date checks if the file won't ever change.
+  if (!isReadonlyFilesystem(path.c_str())) {
+    if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+      // Stat requires execute permissions on all directories path to the file. If the process does
+      // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+      // always have to return true.
+      LOG(WARNING) << "Failed to stat file '" << path << "': "
+                   << base::SystemErrorCodeToString(errno);
+    }
   }
 
   return std::unique_ptr<ZipAssetsProvider>(
@@ -133,12 +139,15 @@
   }
 
   struct stat sb{.st_mtime = -1};
-  if (fstat(released_fd, &sb) < 0) {
-    // Stat requires execute permissions on all directories path to the file. If the process does
-    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
-    // always have to return true.
-    LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
-                 << base::SystemErrorCodeToString(errno);
+  // Skip all up-to-date checks if the file won't ever change.
+  if (!isReadonlyFilesystem(released_fd)) {
+    if (fstat(released_fd, &sb) < 0) {
+      // Stat requires execute permissions on all directories path to the file. If the process does
+      // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+      // always have to return true.
+      LOG(WARNING) << "Failed to fstat file '" << friendly_name
+                   << "': " << base::SystemErrorCodeToString(errno);
+    }
   }
 
   return std::unique_ptr<ZipAssetsProvider>(
@@ -211,8 +220,7 @@
 }
 
 bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
-                                    const std::function<void(const StringPiece&, FileType)>& f)
-                                    const {
+                                    const std::function<void(StringPiece, FileType)>& f) const {
     std::string root_path_full = root_path;
     if (root_path_full.back() != '/') {
       root_path_full += '/';
@@ -238,8 +246,7 @@
       if (!leaf_file_path.empty()) {
         auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
         if (iter != leaf_file_path.end()) {
-          std::string dir =
-              leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+          std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)));
           dirs.insert(std::move(dir));
         } else {
           f(leaf_file_path, kFileTypeRegular);
@@ -277,6 +284,9 @@
 }
 
 bool ZipAssetsProvider::IsUpToDate() const {
+  if (last_mod_time_ == -1) {
+    return true;
+  }
   struct stat sb{};
   if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
     // If fstat fails on the zip archive, return true so the zip archive the resource system does
@@ -290,7 +300,7 @@
     : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
 
 std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
-  struct stat sb{};
+  struct stat sb;
   const int result = stat(path.c_str(), &sb);
   if (result == -1) {
     LOG(ERROR) << "Failed to find directory '" << path << "'.";
@@ -306,8 +316,9 @@
     path += OS_PATH_SEPARATOR;
   }
 
-  return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
-                                                                              sb.st_mtime));
+  const bool isReadonly = isReadonlyFilesystem(path.c_str());
+  return std::unique_ptr<DirectoryAssetsProvider>(
+      new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
 }
 
 std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -324,8 +335,7 @@
 
 bool DirectoryAssetsProvider::ForEachFile(
     const std::string& /* root_path */,
-    const std::function<void(const StringPiece&, FileType)>& /* f */)
-    const {
+    const std::function<void(StringPiece, FileType)>& /* f */) const {
   return true;
 }
 
@@ -338,7 +348,10 @@
 }
 
 bool DirectoryAssetsProvider::IsUpToDate() const {
-  struct stat sb{};
+  if (last_mod_time_ == -1) {
+    return true;
+  }
+  struct stat sb;
   if (stat(dir_.c_str(), &sb) < 0) {
     // If stat fails on the zip archive, return true so the zip archive the resource system does
     // attempt to refresh the ApkAsset.
@@ -373,8 +386,7 @@
 }
 
 bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
-                                      const std::function<void(const StringPiece&, FileType)>& f)
-                                      const {
+                                      const std::function<void(StringPiece, FileType)>& f) const {
   return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
 }
 
@@ -397,8 +409,8 @@
   return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
 }
 
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
-  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
 }
 
 std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
@@ -412,7 +424,7 @@
 
 bool EmptyAssetsProvider::ForEachFile(
     const std::string& /* root_path */,
-    const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+    const std::function<void(StringPiece, FileType)>& /* f */) const {
   return true;
 }
 
@@ -435,4 +447,4 @@
   return true;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
index 19ead95..93a7d17 100644
--- a/libs/androidfw/ConfigDescription.cpp
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -637,7 +637,7 @@
   return true;
 }
 
-bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) {
   std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
 
   ConfigDescription config;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index e122d48..f3d2443 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -274,8 +274,7 @@
        target_apk_path_(target_apk_path),
        idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
 
-std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
-                                               const StringPiece& idmap_data) {
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
   ATRACE_CALL();
   size_t data_size = idmap_data.size();
   auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
@@ -365,7 +364,7 @@
 
   // Can't use make_unique because LoadedIdmap constructor is private.
   return std::unique_ptr<LoadedIdmap>(
-      new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
+      new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
                       target_inline_entries, target_inline_entry_values, configurations,
                       overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
 }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 386f718..c0fdfe2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -645,16 +645,16 @@
         }
 
         std::string name;
-        util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name);
+        util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name);
         std::string actor;
-        util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor);
-
-        if (loaded_package->overlayable_map_.find(name) !=
-            loaded_package->overlayable_map_.end()) {
-          LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
+        util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor);
+        auto [name_to_actor_it, inserted] =
+            loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor));
+        if (!inserted) {
+          LOG(ERROR) << "Multiple <overlayable> blocks with the same name '"
+                     << name_to_actor_it->first << "'.";
           return {};
         }
-        loaded_package->overlayable_map_.emplace(name, actor);
 
         // Iterate over the overlayable policy chunks contained within the overlayable chunk data
         ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -669,7 +669,6 @@
                 LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small.";
                 return {};
               }
-
               if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref))
                   < dtohl(policy_header->entry_count)) {
                 LOG(ERROR) <<  "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries.";
@@ -691,8 +690,8 @@
 
               // Add the pairing of overlayable properties and resource ids to the package
               OverlayableInfo overlayable_info {
-                .name = name,
-                .actor = actor,
+                .name = name_to_actor_it->first,
+                .actor = name_to_actor_it->second,
                 .policy_flags = policy_header->policy_flags
               };
               loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
@@ -736,6 +735,7 @@
         const auto entry_end = entry_begin + dtohl(lib_alias->count);
         std::unordered_set<uint32_t> finalized_ids;
         finalized_ids.reserve(entry_end - entry_begin);
+        loaded_package->alias_id_map_.reserve(entry_end - entry_begin);
         for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
           if (!entry_iter) {
             LOG(ERROR) << "NULL ResTable_staged_alias_entry record??";
@@ -749,13 +749,20 @@
           }
 
           auto staged_id = dtohl(entry_iter->stagedResId);
-          auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
-          if (!success) {
+          loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id);
+        }
+
+        std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(),
+            [](auto&& l, auto&& r) { return l.first < r.first; });
+        const auto duplicate_it =
+            std::adjacent_find(loaded_package->alias_id_map_.begin(),
+                               loaded_package->alias_id_map_.end(),
+                               [](auto&& l, auto&& r) { return l.first == r.first; });
+          if (duplicate_it != loaded_package->alias_id_map_.end()) {
             LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
-                                       staged_id);
+                                       duplicate_it->first);
             return {};
           }
-        }
       } break;
 
       default:
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index d87a3ce..272a988 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -66,7 +66,7 @@
   return std::all_of(std::begin(str), std::end(str), ::isdigit);
 }
 
-bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+bool LocaleValue::InitFromFilterString(StringPiece str) {
   // A locale (as specified in the filter) is an underscore separated name such
   // as "en_US", "en_Latn_US", or "en_US_POSIX".
   std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
@@ -132,11 +132,11 @@
   return true;
 }
 
-bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
   return InitFromBcp47TagImpl(bcp47tag, '-');
 }
 
-bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
   std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
   if (subtags.size() == 1) {
     set_language(subtags[0].c_str());
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 267190a..31516dc 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
 #include <type_traits>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
+#include <android-base/utf8.h>
 #include <androidfw/ByteBucketArray.h>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@
 }
 
 bool IsFabricatedOverlay(const std::string& path) {
-  std::ifstream fin(path);
-  uint32_t magic;
-  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
-    return magic == kFabricatedOverlayMagic;
+  return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+  auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+  if (fd < 0) {
+    return false;
   }
-  return false;
+  return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
+  uint32_t magic;
+  if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+    return false;
+  }
+  return magic == kFabricatedOverlayMagic;
 }
 
 static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -6988,11 +7001,10 @@
 DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
 
 DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
-    : mAssignedPackageId(packageId)
+    : mLookupTable()
+    , mAssignedPackageId(packageId)
     , mAppAsLib(appAsLib)
 {
-    memset(mLookupTable, 0, sizeof(mLookupTable));
-
     // Reserved package ids
     mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
     mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7073,10 +7085,6 @@
     mLookupTable[buildPackageId] = runtimePackageId;
 }
 
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
-  mAliasId[stagedId] = finalizedId;
-}
-
 status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
     uint32_t res = *resId;
     size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7086,11 +7094,12 @@
         return NO_ERROR;
     }
 
-    auto alias_id = mAliasId.find(res);
-    if (alias_id != mAliasId.end()) {
+    const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+        [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+    if (alias_it != mAliasId.end() && alias_it->first == res) {
       // Rewrite the resource id to its alias resource id. Since the alias resource id is a
       // compile-time id, it still needs to be resolved further.
-      res = alias_id->second;
+      res = alias_it->second;
     }
 
     if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 87fb2c0..ccb6156 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -18,7 +18,7 @@
 
 namespace android {
 
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry) {
   *out_package = "";
   *out_type = "";
@@ -33,16 +33,16 @@
   while (current != end) {
     if (out_type->size() == 0 && *current == '/') {
       has_type_separator = true;
-      out_type->assign(start, current - start);
+      *out_type = StringPiece(start, current - start);
       start = current + 1;
     } else if (out_package->size() == 0 && *current == ':') {
       has_package_separator = true;
-      out_package->assign(start, current - start);
+      *out_package = StringPiece(start, current - start);
       start = current + 1;
     }
     current++;
   }
-  out_entry->assign(start, end - start);
+  *out_entry = StringPiece(start, end - start);
 
   return !(has_package_separator && out_package->empty()) &&
          !(has_type_separator && out_type->empty());
@@ -50,7 +50,7 @@
 
 base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
     const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
-    const StringPiece& package_name) {
+    StringPiece package_name) {
   AssetManager2::ResourceName name{
     .package = package_name.data(),
     .package_len = package_name.size(),
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index b59e906..1cb8df3 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -161,16 +161,15 @@
   return entry_->context;
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
+StringPool::Ref StringPool::MakeRef(StringPiece str) {
   return MakeRefImpl(str, Context{}, true);
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
+StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) {
   return MakeRefImpl(str, context, true);
 }
 
-StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
-                                        bool unique) {
+StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) {
   if (unique) {
     auto range = indexed_strings_.equal_range(str);
     for (auto iter = range.first; iter != range.second; ++iter) {
@@ -181,7 +180,7 @@
   }
 
   std::unique_ptr<Entry> entry(new Entry());
-  entry->value = str.to_string();
+  entry->value = std::string(str);
   entry->context = context;
   entry->index_ = strings_.size();
   entry->ref_ = 0;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 52ad0dc..be55fe8 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -42,7 +42,7 @@
   }
 }
 
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
   ssize_t utf16_length =
       utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
   if (utf16_length <= 0) {
@@ -56,7 +56,7 @@
   return utf16;
 }
 
-std::string Utf16ToUtf8(const StringPiece16& utf16) {
+std::string Utf16ToUtf8(StringPiece16 utf16) {
   ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
   if (utf8_length <= 0) {
     return {};
@@ -68,7 +68,7 @@
   return utf8;
 }
 
-std::string Utf8ToModifiedUtf8(const std::string& utf8) {
+std::string Utf8ToModifiedUtf8(std::string_view utf8) {
   // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode
   // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format
   // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8
@@ -86,7 +86,7 @@
 
   // Early out if no 4 byte codepoints are found
   if (size == modified_size) {
-    return utf8;
+    return std::string(utf8);
   }
 
   std::string output;
@@ -115,7 +115,7 @@
   return output;
 }
 
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) {
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) {
   // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8
   // representation.
   std::string output;
@@ -170,30 +170,28 @@
   return output;
 }
 
-static std::vector<std::string> SplitAndTransform(
-    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+template <class Func>
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) {
   std::vector<std::string> parts;
   const StringPiece::const_iterator end = std::end(str);
   StringPiece::const_iterator start = std::begin(str);
   StringPiece::const_iterator current;
   do {
     current = std::find(start, end, sep);
-    parts.emplace_back(str.substr(start, current).to_string());
-    if (f) {
-      std::string& part = parts.back();
-      std::transform(part.begin(), part.end(), part.begin(), f);
-    }
+    parts.emplace_back(StringPiece(start, current - start));
+    std::string& part = parts.back();
+    std::transform(part.begin(), part.end(), part.begin(), f);
     start = current + 1;
   } while (current != end);
   return parts;
 }
 
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
-  return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+  return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); });
 }
 
 std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
-  std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+  auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
   uint8_t* p = data.get();
   for (const auto& block : buffer) {
     memcpy(p, block.buffer.get(), block.size);
@@ -211,7 +209,7 @@
 
 std::string GetString(const android::ResStringPool& pool, size_t idx) {
   if (auto str = pool.string8At(idx); str.ok()) {
-    return ModifiedUtf8ToUtf8(str->to_string());
+    return ModifiedUtf8ToUtf8(*str);
   }
   return Utf16ToUtf8(GetString16(pool, idx));
 }
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 1bde792..e4d1218 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,6 +17,7 @@
 #ifndef ANDROIDFW_ASSETMANAGER2_H_
 #define ANDROIDFW_ASSETMANAGER2_H_
 
+#include "android-base/function_ref.h"
 #include "android-base/macros.h"
 
 #include <array>
@@ -124,8 +125,7 @@
   uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
 
   // Returns a string representation of the overlayable API of a package.
-  bool GetOverlayablesToString(const android::StringPiece& package_name,
-                               std::string* out) const;
+  bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const;
 
   const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(
       uint32_t package_id) const;
@@ -321,17 +321,8 @@
   // Creates a new Theme from this AssetManager.
   std::unique_ptr<Theme> NewTheme();
 
-  void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func,
-                      package_property_t excluded_property_flags = 0U) const {
-    for (const PackageGroup& package_group : package_groups_) {
-      const auto loaded_package = package_group.packages_.front().loaded_package_;
-      if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
-          && !func(loaded_package->GetPackageName(),
-                   package_group.dynamic_ref_table->mAssignedPackageId)) {
-        return;
-      }
-    }
-  }
+  void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+                      package_property_t excluded_property_flags = 0U) const;
 
   void DumpToLog() const;
 
@@ -572,6 +563,7 @@
   AssetManager2* asset_manager_ = nullptr;
   uint32_t type_spec_flags_ = 0u;
 
+  std::vector<uint32_t> keys_;
   std::vector<Entry> entries_;
 };
 
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 966ec74..7891194 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -46,7 +46,7 @@
   // Iterate over all files and directories provided by the interface. The order of iteration is
   // stable.
   virtual bool ForEachFile(const std::string& path,
-                           const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+                           const std::function<void(StringPiece, FileType)>& f) const = 0;
 
   // Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an
   // APk, a directory, or some other file type.
@@ -80,8 +80,8 @@
 
 // Supplies assets from a zip archive.
 struct ZipAssetsProvider : public AssetsProvider {
-  static std::unique_ptr<ZipAssetsProvider> Create(std::string path,
-                                                   package_property_t flags);
+  static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags,
+                                                   base::unique_fd fd = {});
 
   static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
                                                    std::string friendly_name,
@@ -90,7 +90,7 @@
                                                    off64_t len = kUnknownLength);
 
   bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -132,7 +132,7 @@
   static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
 
   bool ForEachFile(const std::string& path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -157,7 +157,7 @@
                                                 std::unique_ptr<AssetsProvider>&& secondary);
 
   bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -181,10 +181,10 @@
 // Does not provide any assets.
 struct EmptyAssetsProvider : public AssetsProvider {
   static std::unique_ptr<AssetsProvider> Create();
-  static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+  static std::unique_ptr<AssetsProvider> Create(std::string path);
 
   bool ForEachFile(const std::string& path,
-                  const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 949c9445..ca0a9ed 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,6 +17,7 @@
 #ifndef __BYTE_BUCKET_ARRAY_H
 #define __BYTE_BUCKET_ARRAY_H
 
+#include <algorithm>
 #include <cstdint>
 #include <cstring>
 
@@ -31,14 +32,16 @@
 template <typename T>
 class ByteBucketArray {
  public:
-  ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+  ByteBucketArray() {
+    memset(buckets_, 0, sizeof(buckets_));
+  }
 
   ~ByteBucketArray() {
-    for (size_t i = 0; i < kNumBuckets; i++) {
-      if (buckets_[i] != NULL) {
-        delete[] buckets_[i];
-      }
-    }
+    deleteBuckets();
+  }
+
+  void clear() {
+    deleteBuckets();
     memset(buckets_, 0, sizeof(buckets_));
   }
 
@@ -53,7 +56,7 @@
 
     uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
     T* bucket = buckets_[bucket_index];
-    if (bucket == NULL) {
+    if (bucket == nullptr) {
       return default_;
     }
     return bucket[0x0f & static_cast<uint8_t>(index)];
@@ -64,9 +67,9 @@
                           << ") with size=" << size();
 
     uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
-    T* bucket = buckets_[bucket_index];
-    if (bucket == NULL) {
-      bucket = buckets_[bucket_index] = new T[kBucketSize]();
+    T*& bucket = buckets_[bucket_index];
+    if (bucket == nullptr) {
+      bucket = new T[kBucketSize]();
     }
     return bucket[0x0f & static_cast<uint8_t>(index)];
   }
@@ -80,11 +83,44 @@
     return true;
   }
 
+  template <class Func>
+  void forEachItem(Func f) {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      const auto bucket = buckets_[i];
+      if (bucket != nullptr) {
+        for (size_t j = 0; j < kBucketSize; j++) {
+          f((i << 4) | j, bucket[j]);
+        }
+      }
+    }
+  }
+
+  template <class Func>
+  void trimBuckets(Func isEmptyFunc) {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      const auto bucket = buckets_[i];
+      if (bucket != nullptr) {
+        if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) {
+          delete[] bucket;
+          buckets_[i] = nullptr;
+        }
+      }
+    }
+  }
+
  private:
   enum { kNumBuckets = 16, kBucketSize = 16 };
 
+  void deleteBuckets() {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      if (buckets_[i] != nullptr) {
+        delete[] buckets_[i];
+      }
+    }
+  }
+
   T* buckets_[kNumBuckets];
-  T default_;
+  static inline const T default_ = {};
 };
 
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index 61d10cd..71087cd 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -72,7 +72,7 @@
    * The resulting configuration has the appropriate sdkVersion defined
    * for backwards compatibility.
    */
-  static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
+  static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr);
 
   /**
    * If the configuration uses an axis that was added after
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 273b05a..4d5844e 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -35,7 +35,7 @@
  public:
   DiagMessage() = default;
 
-  explicit DiagMessage(const android::StringPiece& src) : source_(src) {
+  explicit DiagMessage(android::StringPiece src) : source_(src) {
   }
 
   explicit DiagMessage(const Source& src) : source_(src) {
@@ -61,7 +61,7 @@
 
 template <>
 inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) {
-  message_ << android::StringPiece16(value);
+  message_ << value;
   return *this;
 }
 
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index a1cbbbf..f173e6e 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -157,8 +157,7 @@
 class LoadedIdmap {
  public:
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
-  static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
-                                           const StringPiece& idmap_data);
+  static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
 
   // Returns the path to the IDMAP.
   std::string_view IdmapPath() const {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 79d96282..4d12885 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,8 +99,8 @@
 };
 
 struct OverlayableInfo {
-  std::string name;
-  std::string actor;
+  std::string_view name;
+  std::string_view actor;
   uint32_t policy_flags;
 };
 
@@ -275,7 +275,7 @@
     return overlayable_map_;
   }
 
-  const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+  const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
     return alias_id_map_;
   }
 
@@ -295,8 +295,8 @@
   std::unordered_map<uint8_t, TypeSpec> type_specs_;
   ByteBucketArray<uint32_t> resource_ids_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
-  std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
-  std::map<uint32_t, uint32_t> alias_id_map_;
+  std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+  std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
 
   // A map of overlayable name to actor
   std::unordered_map<std::string, std::string> overlayable_map_;
diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h
index 484ed79..8934bed 100644
--- a/libs/androidfw/include/androidfw/Locale.h
+++ b/libs/androidfw/include/androidfw/Locale.h
@@ -39,10 +39,10 @@
   /**
    * Initialize this LocaleValue from a config string.
    */
-  bool InitFromFilterString(const android::StringPiece& config);
+  bool InitFromFilterString(android::StringPiece config);
 
   // Initializes this LocaleValue from a BCP-47 locale tag.
-  bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+  bool InitFromBcp47Tag(android::StringPiece bcp47tag);
 
   /**
    * Initialize this LocaleValue from parts of a vector.
@@ -70,7 +70,7 @@
   inline bool operator>(const LocaleValue& o) const;
 
  private:
-  bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+  bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator);
 
   void set_language(const char* language);
   void set_region(const char* language);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index b2b95b7..52321da 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
 #define _LIBS_UTILS_RESOURCE_TYPES_H
 
 #include <android-base/expected.h>
+#include <android-base/unique_fd.h>
 
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
@@ -58,6 +59,8 @@
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1882,7 +1885,10 @@
 
     void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
 
-    void addAlias(uint32_t stagedId, uint32_t finalizedId);
+    using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
+    void setAliases(AliasMap aliases) {
+        mAliasId = std::move(aliases);
+    }
 
     // Returns whether or not the value must be looked up.
     bool requiresLookup(const Res_value* value) const;
@@ -1896,12 +1902,12 @@
         return mEntries;
     }
 
-private:
-    uint8_t                         mAssignedPackageId;
-    uint8_t                         mLookupTable[256];
-    KeyedVector<String16, uint8_t>  mEntries;
-    bool                            mAppAsLib;
-    std::map<uint32_t, uint32_t>    mAliasId;
+   private:
+    uint8_t mLookupTable[256];
+    uint8_t mAssignedPackageId;
+    bool mAppAsLib;
+    KeyedVector<String16, uint8_t> mEntries;
+    AliasMap mAliasId;
 };
 
 bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index bd1c440..2d90a52 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -25,14 +25,14 @@
 // Extracts the package, type, and name from a string of the format: [[package:]type/]name
 // Validation must be performed on each extracted piece.
 // Returns false if there was a syntax error.
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry);
 
 // Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
 // Useful for getting resource name without re-running AssetManager2::FindEntry searches.
 base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
     const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
-    const StringPiece& package_name);
+    StringPiece package_name);
 
 // Formats a ResourceName to "package:type/entry_name".
 std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name);
diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h
index 0421a91..ddc9ba4 100644
--- a/libs/androidfw/include/androidfw/Source.h
+++ b/libs/androidfw/include/androidfw/Source.h
@@ -34,15 +34,14 @@
 
   Source() = default;
 
-  inline Source(const android::StringPiece& path) : path(path.to_string()) {  // NOLINT(implicit)
+  inline Source(android::StringPiece path) : path(path) {  // NOLINT(implicit)
   }
 
-  inline Source(const android::StringPiece& path, const android::StringPiece& archive)
-      : path(path.to_string()), archive(archive.to_string()) {
+  inline Source(android::StringPiece path, android::StringPiece archive)
+      : path(path), archive(archive) {
   }
 
-  inline Source(const android::StringPiece& path, size_t line)
-      : path(path.to_string()), line(line) {
+  inline Source(android::StringPiece path, size_t line) : path(path), line(line) {
   }
 
   inline Source WithLine(size_t line) const {
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
index fac2fa4..f6cc64e 100644
--- a/libs/androidfw/include/androidfw/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -19,307 +19,37 @@
 
 #include <ostream>
 #include <string>
+#include <string_view>
 
-#include "utils/JenkinsHash.h"
 #include "utils/Unicode.h"
 
 namespace android {
 
-// Read only wrapper around basic C strings. Prevents excessive copying.
-// StringPiece does not own the data it is wrapping. The lifetime of the underlying
-// data must outlive this StringPiece.
-//
-// WARNING: When creating from std::basic_string<>, moving the original
-// std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
-// BasicStringPiece<> should only be used transitively.
-//
-// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(),
-// passing an std::string will first copy the string, then create a StringPiece
-// on the copy, which is then immediately destroyed.
-// Instead, create a StringPiece explicitly:
-//
-// std::string my_string = "foo";
-// std::make_pair<StringPiece, T>(StringPiece(my_string), ...);
-template <typename TChar>
-class BasicStringPiece {
- public:
-  using const_iterator = const TChar*;
-  using difference_type = size_t;
-  using size_type = size_t;
-
-  // End of string marker.
-  constexpr static const size_t npos = static_cast<size_t>(-1);
-
-  BasicStringPiece();
-  BasicStringPiece(const BasicStringPiece<TChar>& str);
-  BasicStringPiece(const std::basic_string<TChar>& str);  // NOLINT(google-explicit-constructor)
-  BasicStringPiece(const TChar* str);                     // NOLINT(google-explicit-constructor)
-  BasicStringPiece(const TChar* str, size_t len);
-
-  BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
-  BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
-
-  BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
-  BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
-                                 BasicStringPiece<TChar>::const_iterator end) const;
-
-  const TChar* data() const;
-  size_t length() const;
-  size_t size() const;
-  bool empty() const;
-  std::basic_string<TChar> to_string() const;
-
-  bool contains(const BasicStringPiece<TChar>& rhs) const;
-  int compare(const BasicStringPiece<TChar>& rhs) const;
-  bool operator<(const BasicStringPiece<TChar>& rhs) const;
-  bool operator>(const BasicStringPiece<TChar>& rhs) const;
-  bool operator==(const BasicStringPiece<TChar>& rhs) const;
-  bool operator!=(const BasicStringPiece<TChar>& rhs) const;
-
-  const_iterator begin() const;
-  const_iterator end() const;
-
- private:
-  const TChar* data_;
-  size_t length_;
-};
+template <class T>
+using BasicStringPiece = std::basic_string_view<T>;
 
 using StringPiece = BasicStringPiece<char>;
 using StringPiece16 = BasicStringPiece<char16_t>;
 
-//
-// BasicStringPiece implementation.
-//
-
-template <typename TChar>
-constexpr const size_t BasicStringPiece<TChar>::npos;
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str)
-    : data_(str.data_), length_(str.length_) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str)
-    : data_(str.data()), length_(str.length()) {}
-
-template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str)
-    : data_(str), length_(str != nullptr ? strlen(str) : 0) {}
-
-template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
-    : data_(str), length_(str != nullptr ? strlen16(str) : 0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
-    : data_(str), length_(len) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
-    const BasicStringPiece<TChar>& rhs) {
-  data_ = rhs.data_;
-  length_ = rhs.length_;
-  return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
-  data_ = str;
-  length_ = len;
-  return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
-  if (len == npos) {
-    len = length_ - start;
-  }
-
-  if (start > length_ || start + len > length_) {
-    return BasicStringPiece<TChar>();
-  }
-  return BasicStringPiece<TChar>(data_ + start, len);
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
-    BasicStringPiece<TChar>::const_iterator begin,
-    BasicStringPiece<TChar>::const_iterator end) const {
-  return BasicStringPiece<TChar>(begin, end - begin);
-}
-
-template <typename TChar>
-inline const TChar* BasicStringPiece<TChar>::data() const {
-  return data_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::length() const {
-  return length_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::size() const {
-  return length_;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::empty() const {
-  return length_ == 0;
-}
-
-template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const {
-  return std::basic_string<TChar>(data_, length_);
-}
-
-template <>
-inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
-  if (!data_ || !rhs.data_) {
-    return false;
-  }
-  if (rhs.length_ > length_) {
-    return false;
-  }
-  return strstr(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
-  const char nullStr = '\0';
-  const char* b1 = data_ != nullptr ? data_ : &nullStr;
-  const char* e1 = b1 + length_;
-  const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
-  const char* e2 = b2 + rhs.length_;
-
-  while (b1 < e1 && b2 < e2) {
-    const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
-    if (d) {
-      return d;
-    }
-  }
-  return static_cast<int>(length_ - rhs.length_);
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
-  const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size());
-  if (result_len < 0) {
-    // Empty string.
-    return out;
-  }
-
-  std::string result;
-  result.resize(static_cast<size_t>(result_len));
-  utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1);
-  return out << result;
-}
-
-template <>
-inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
-  if (!data_ || !rhs.data_) {
-    return false;
-  }
-  if (rhs.length_ > length_) {
-    return false;
-  }
-  return strstr16(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
-  const char16_t nullStr = u'\0';
-  const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
-  const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
-  return strzcmp16(b1, length_, b2, rhs.length_);
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) < 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) > 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) == 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) != 0;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
-  return data_;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
-  return data_ + length_;
-}
-
-template <typename TChar>
-inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
-  return out.write(str.data(), str.size());
-}
-
-template <typename TChar>
-inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs,
-                                              const BasicStringPiece<TChar>& rhs) {
-  return lhs.append(rhs.data(), rhs.size());
-}
-
-template <typename TChar>
-inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
 }  // namespace android
 
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+namespace std {
+
+inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) {
   ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size());
   if (utf8_len < 0) {
-    return out << "???";
+    return out;  // empty
   }
 
   std::string utf8;
   utf8.resize(static_cast<size_t>(utf8_len));
-  utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1);
+  utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1);
   return out << utf8;
 }
 
-namespace std {
-
-template <typename TChar>
-struct hash<android::BasicStringPiece<TChar>> {
-  size_t operator()(const android::BasicStringPiece<TChar>& str) const {
-    uint32_t hashCode = android::JenkinsHashMixBytes(
-        0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
-    return static_cast<size_t>(hashCode);
-  }
-};
+inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) {
+  return out << std::u16string_view(str);
+}
 
 }  // namespace std
 
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 25174d8..0190ab5 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -167,11 +167,11 @@
 
   // Adds a string to the pool, unless it already exists. Returns a reference to the string in the
   // pool.
-  Ref MakeRef(const android::StringPiece& str);
+  Ref MakeRef(android::StringPiece str);
 
   // Adds a string to the pool, unless it already exists, with a context object that can be used
   // when sorting the string pool. Returns a reference to the string in the pool.
-  Ref MakeRef(const android::StringPiece& str, const Context& context);
+  Ref MakeRef(android::StringPiece str, const Context& context);
 
   // Adds a string from another string pool. Returns a reference to the string in the string pool.
   Ref MakeRef(const Ref& ref);
@@ -215,7 +215,7 @@
 
   static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
 
-  Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+  Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique);
   void ReAssignIndices();
 
   std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 1bbc7f5..ae7c65f 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -123,16 +123,16 @@
 void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
 
 // Converts a UTF-8 string to a UTF-16 string.
-std::u16string Utf8ToUtf16(const StringPiece& utf8);
+std::u16string Utf8ToUtf16(StringPiece utf8);
 
 // Converts a UTF-16 string to a UTF-8 string.
-std::string Utf16ToUtf8(const StringPiece16& utf16);
+std::string Utf16ToUtf8(StringPiece16 utf16);
 
 // Converts a UTF8 string into Modified UTF8
-std::string Utf8ToModifiedUtf8(const std::string& utf8);
+std::string Utf8ToModifiedUtf8(std::string_view utf8);
 
 // Converts a Modified UTF8 string into a UTF8 string
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8);
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8);
 
 inline uint16_t HostToDevice16(uint16_t value) {
   return htods(value);
@@ -150,7 +150,7 @@
   return dtohl(value);
 }
 
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
 
 template <typename T>
 inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 5a5a0e2..d40d24e 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -44,6 +44,10 @@
 /* get the file's modification date; returns -1 w/errno set on failure */
 time_t getFileModDate(const char* fileName);
 
+// Check if |path| or |fd| resides on a readonly filesystem.
+bool isReadonlyFilesystem(const char* path);
+bool isReadonlyFilesystem(int fd);
+
 }; // namespace android
 
 #endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 5285420..7af5066 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -21,12 +21,17 @@
 //
 #include <androidfw/misc.h>
 
-#include <sys/stat.h>
-#include <cstring>
-#include <errno.h>
-#include <cstdio>
+#include "android-base/logging.h"
 
-using namespace android;
+#ifndef _WIN32
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#endif  // _WIN32
+
+#include <cstring>
+#include <cstdio>
+#include <errno.h>
+#include <sys/stat.h>
 
 namespace android {
 
@@ -41,8 +46,7 @@
         if (errno == ENOENT || errno == ENOTDIR)
             return kFileTypeNonexistent;
         else {
-            fprintf(stderr, "getFileType got errno=%d on '%s'\n",
-                errno, fileName);
+            PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
             return kFileTypeUnknown;
         }
     } else {
@@ -82,4 +86,32 @@
     return sb.st_mtime;
 }
 
+#ifdef _WIN32
+// No need to implement these for Windows, the functions only matter on a device.
+bool isReadonlyFilesystem(const char*) {
+    return false;
+}
+bool isReadonlyFilesystem(int) {
+    return false;
+}
+#else   // _WIN32
+bool isReadonlyFilesystem(const char* path) {
+    struct statfs sfs;
+    if (::statfs(path, &sfs)) {
+        PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
+        return false;
+    }
+    return (sfs.f_flags & ST_RDONLY) != 0;
+}
+
+bool isReadonlyFilesystem(int fd) {
+    struct statfs sfs;
+    if (::fstatfs(fd, &sfs)) {
+        PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed";
+        return false;
+    }
+    return (sfs.f_flags & ST_RDONLY) != 0;
+}
+#endif  // _WIN32
+
 }; // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
index ddd8ab8..1c89c61 100644
--- a/libs/androidfw/tests/AttributeResolution_bench.cpp
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -120,8 +120,8 @@
     return;
   }
 
-  std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie,
-                                                           Asset::ACCESS_BUFFER);
+  std::unique_ptr<Asset> asset =
+      assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER);
   if (asset == nullptr) {
     state.SkipWithError("failed to load layout");
     return;
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
index 5d464c7..9c36cfb 100644
--- a/libs/androidfw/tests/ByteBucketArray_test.cpp
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -52,4 +52,57 @@
   }
 }
 
+TEST(ByteBucketArrayTest, TestForEach) {
+  ByteBucketArray<int> bba;
+  ASSERT_TRUE(bba.set(0, 1));
+  ASSERT_TRUE(bba.set(10, 2));
+  ASSERT_TRUE(bba.set(26, 3));
+  ASSERT_TRUE(bba.set(129, 4));
+  ASSERT_TRUE(bba.set(234, 5));
+
+  int count = 0;
+  bba.forEachItem([&count](auto i, auto val) {
+    ++count;
+    switch (i) {
+      case 0:
+        EXPECT_EQ(1, val);
+        break;
+      case 10:
+        EXPECT_EQ(2, val);
+        break;
+      case 26:
+        EXPECT_EQ(3, val);
+        break;
+      case 129:
+        EXPECT_EQ(4, val);
+        break;
+      case 234:
+        EXPECT_EQ(5, val);
+        break;
+      default:
+        EXPECT_EQ(0, val);
+        break;
+    }
+  });
+  ASSERT_EQ(4 * 16, count);
+}
+
+TEST(ByteBucketArrayTest, TestTrimBuckets) {
+  ByteBucketArray<int> bba;
+  ASSERT_TRUE(bba.set(0, 1));
+  ASSERT_TRUE(bba.set(255, 2));
+  {
+    bba.trimBuckets([](auto val) { return val < 2; });
+    int count = 0;
+    bba.forEachItem([&count](auto, auto) { ++count; });
+    ASSERT_EQ(1 * 16, count);
+  }
+  {
+    bba.trimBuckets([](auto val) { return val < 3; });
+    int count = 0;
+    bba.forEachItem([&count](auto, auto) { ++count; });
+    ASSERT_EQ(0, count);
+  }
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
index ce7f805..8fed0a4 100644
--- a/libs/androidfw/tests/ConfigDescription_test.cpp
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -25,8 +25,8 @@
 
 namespace android {
 
-static ::testing::AssertionResult TestParse(
-    const StringPiece& input, ConfigDescription* config = nullptr) {
+static ::testing::AssertionResult TestParse(StringPiece input,
+                                            ConfigDescription* config = nullptr) {
   if (ConfigDescription::Parse(input, config)) {
     return ::testing::AssertionSuccess() << input << " was successfully parsed";
   }
@@ -138,7 +138,7 @@
   EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
 }
 
-static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
+static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) {
   ConfigDescription config;
   CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
   return config;
diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp
index 316a5c1..822e527 100644
--- a/libs/androidfw/tests/StringPiece_test.cpp
+++ b/libs/androidfw/tests/StringPiece_test.cpp
@@ -60,36 +60,4 @@
   EXPECT_TRUE(StringPiece(car) > banana);
 }
 
-TEST(StringPieceTest, ContainsOtherStringPiece) {
-  StringPiece text("I am a leaf on the wind.");
-  StringPiece start_needle("I am");
-  StringPiece end_needle("wind.");
-  StringPiece middle_needle("leaf");
-  StringPiece empty_needle("");
-  StringPiece missing_needle("soar");
-  StringPiece long_needle("This string is longer than the text.");
-
-  EXPECT_TRUE(text.contains(start_needle));
-  EXPECT_TRUE(text.contains(end_needle));
-  EXPECT_TRUE(text.contains(middle_needle));
-  EXPECT_TRUE(text.contains(empty_needle));
-  EXPECT_FALSE(text.contains(missing_needle));
-  EXPECT_FALSE(text.contains(long_needle));
-
-  StringPiece16 text16(u"I am a leaf on the wind.");
-  StringPiece16 start_needle16(u"I am");
-  StringPiece16 end_needle16(u"wind.");
-  StringPiece16 middle_needle16(u"leaf");
-  StringPiece16 empty_needle16(u"");
-  StringPiece16 missing_needle16(u"soar");
-  StringPiece16 long_needle16(u"This string is longer than the text.");
-
-  EXPECT_TRUE(text16.contains(start_needle16));
-  EXPECT_TRUE(text16.contains(end_needle16));
-  EXPECT_TRUE(text16.contains(middle_needle16));
-  EXPECT_TRUE(text16.contains(empty_needle16));
-  EXPECT_FALSE(text16.contains(missing_needle16));
-  EXPECT_FALSE(text16.contains(long_needle16));
-}
-
 }  // namespace android
diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp
index 047d457..0e0acae 100644
--- a/libs/androidfw/tests/StringPool_test.cpp
+++ b/libs/androidfw/tests/StringPool_test.cpp
@@ -321,15 +321,15 @@
   ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
   auto str = test.string8At(0);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80"));
+  EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80"));
 
   str = test.string8At(1);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
+  EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
 
   str = test.string8At(2);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
+  EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
 
   // Check that retrieving the strings returns the original UTF-8 character bytes
   EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80"));
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
new file mode 100644
index 0000000..6dba6c1
--- /dev/null
+++ b/libs/hwui/jni/Mesh.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include <GLES/gl.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
+                                            jboolean isDirect) {
+    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
+    auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
+    return vertexBuffer;
+}
+
+sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
+                                          jboolean isDirect) {
+    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
+    auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
+    return indexBuffer;
+}
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                  jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
+                  jint right, jint bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
+            genVertexBuffer(env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
+                             vertexOffset, nullptr, skRect);
+    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+    return reinterpret_cast<jlong>(meshPtr.release());
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+                         jint indexOffset, jint left, jint top, jint right, jint bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    sk_sp<SkMesh::VertexBuffer> skVertexBuffer = genVertexBuffer(
+            env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isVertexDirect);
+    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);
+    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+    return reinterpret_cast<jlong>(meshPtr.release());
+}
+
+static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
+    auto mesh = wrapper->mesh;
+    if (indexed) {
+        wrapper->mesh = SkMesh::MakeIndexed(
+                sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+                mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
+                mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+    } else {
+        wrapper->mesh = SkMesh::Make(
+                sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+                mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+    }
+}
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                      const char* uniformName, const float values[], int count,
+                                      bool isColor) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+                                jint count) {
+    auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, uniformName);
+    const float values[4] = {value1, value2, value3, value4};
+    nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+                                     jfloatArray jvalues, jboolean isColor) {
+    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+    nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
+                              isColor);
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                    const char* uniformName, const int values[], int count) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+                              jint value1, jint value2, jint value3, jint value4, jint count) {
+    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, uniformName);
+    const int values[4] = {value1, value2, value3, value4};
+    nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+                                   jintArray values) {
+    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, uniformName);
+    AutoJavaIntArray autoValues(env, values, 0);
+    nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+}
+
+static void MeshWrapper_destroy(MeshWrapper* wrapper) {
+    delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+        {"nativeMake", "(JILjava/nio/Buffer;ZIIIIII)J", (void*)make},
+        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIIIII)J",
+         (void*)makeIndexed},
+        {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+    return 0;
+}
+
+}  // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
new file mode 100644
index 0000000..aa014a5
--- /dev/null
+++ b/libs/hwui/jni/Mesh.h
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
+#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
+
+#include <SkMesh.h>
+#include <jni.h>
+
+#include <utility>
+
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
+            : mEnv(env), mBuffer(buffer) {
+        if (buffer == nullptr) {
+            mDataBase = nullptr;
+            mData = nullptr;
+            jniThrowNullPointerException(env);
+        } else {
+            mArray = (jarray) nullptr;
+            if (isDirect) {
+                mData = getDirectBufferPointer(mEnv, mBuffer);
+            } else {
+                mData = setIndirectData(size);
+            }
+        }
+    }
+
+    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+    ~ScopedJavaNioBuffer() { reset(); }
+
+    void reset() {
+        if (mDataBase) {
+            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+            mDataBase = nullptr;
+        }
+    }
+
+    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+        if (this != &rhs) {
+            reset();
+
+            mEnv = rhs.mEnv;
+            mBuffer = rhs.mBuffer;
+            mDataBase = rhs.mDataBase;
+            mData = rhs.mData;
+            mArray = rhs.mArray;
+            rhs.mEnv = nullptr;
+            rhs.mData = nullptr;
+            rhs.mBuffer = nullptr;
+            rhs.mArray = nullptr;
+            rhs.mDataBase = nullptr;
+        }
+        return *this;
+    }
+
+    const void* data() const { return mData; }
+
+private:
+    /**
+     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+     * from a java.nio.Buffer.
+     */
+    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+        if (buffer == nullptr) {
+            return nullptr;
+        }
+
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        if (pointer == 0) {
+            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+                              "Must use a native order direct Buffer");
+            return nullptr;
+        }
+        pointer += position << elementSizeShift;
+        return reinterpret_cast<void*>(pointer);
+    }
+
+    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+    }
+
+    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+                            jint* offset) {
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        *remaining = (limit - position) << elementSizeShift;
+        if (pointer != 0L) {
+            *array = nullptr;
+            pointer += position << elementSizeShift;
+            return reinterpret_cast<void*>(pointer);
+        }
+
+        *array = jniGetNioBufferBaseArray(env, buffer);
+        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+        return nullptr;
+    }
+
+    /**
+     * This is a copy of
+     * static void android_glBufferData__IILjava_nio_Buffer_2I
+     * from com_google_android_gles_jni_GLImpl.cpp
+     */
+    void* setIndirectData(jint size) {
+        jint exception;
+        const char* exceptionType;
+        const char* exceptionMessage;
+        jint bufferOffset = (jint)0;
+        jint remaining;
+        void* tempData;
+
+        if (mBuffer) {
+            tempData =
+                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+            if (remaining < size) {
+                exception = 1;
+                exceptionType = "java/lang/IllegalArgumentException";
+                exceptionMessage = "remaining() < size < needed";
+                goto exit;
+            }
+        }
+        if (mBuffer && tempData == nullptr) {
+            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+            tempData = (void*)(mDataBase + bufferOffset);
+        }
+        return tempData;
+    exit:
+        if (mArray) {
+            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+        }
+        if (exception) {
+            jniThrowException(mEnv, exceptionType, exceptionMessage);
+        }
+        return nullptr;
+    }
+
+    JNIEnv* mEnv;
+
+    // Java Buffer data
+    void* mData;
+    jobject mBuffer;
+
+    // Indirect Buffer Data
+    jarray mArray;
+    char* mDataBase;
+};
+
+class MeshUniformBuilder {
+public:
+    struct MeshUniform {
+        template <typename T>
+        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+                const T& val) {
+            if (!fVar) {
+                SkDEBUGFAIL("Assigning to missing variable");
+            } else if (sizeof(val) != fVar->sizeInBytes()) {
+                SkDEBUGFAIL("Incorrect value size");
+            } else {
+                memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), &val,
+                       szeof(val));
+            }
+        }
+
+        MeshUniform& operator=(const SkMatrix& val) {
+            if (!fVar) {
+                SkDEBUGFAIL("Assigning to missing variable");
+            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+                SkDEBUGFAIL("Incorrect value size");
+            } else {
+                float* data =
+                        SkTAddOffset<float>(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset);
+                data[0] = val.get(0);
+                data[1] = val.get(3);
+                data[2] = val.get(6);
+                data[3] = val.get(1);
+                data[4] = val.get(4);
+                data[5] = val.get(7);
+                data[6] = val.get(2);
+                data[7] = val.get(5);
+                data[8] = val.get(8);
+            }
+            return *this;
+        }
+
+        template <typename T>
+        bool set(const T val[], const int count) {
+            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+            if (!fVar) {
+                SkDEBUGFAIL("Assigning to missing variable");
+                return false;
+            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+                SkDEBUGFAIL("Incorrect value size");
+                return false;
+            } else {
+                memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), val,
+                       sizeof(T) * count);
+            }
+            return true;
+        }
+
+        MeshUniformBuilder* fOwner;
+        const SkRuntimeEffect::Uniform* fVar;
+    };
+    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+        fMeshSpec = sk_sp(meshSpec);
+    }
+
+    sk_sp<SkData> fUniforms;
+
+private:
+    void* writableUniformData() {
+        if (!fUniforms->unique()) {
+            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+        }
+        return fUniforms->writable_data();
+    }
+
+    sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+struct MeshWrapper {
+    SkMesh mesh;
+    MeshUniformBuilder builder;
+};
+#endif  // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
new file mode 100644
index 0000000..22fa4d3
--- /dev/null
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#include <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+using Attribute = SkMeshSpecification::Attribute;
+using Varying = SkMeshSpecification::Varying;
+
+static struct {
+    jclass clazz{};
+    jfieldID type{};
+    jfieldID offset{};
+    jfieldID name{};
+} gAttributeInfo;
+
+static struct {
+    jclass clazz{};
+    jfieldID type{};
+    jfieldID name{};
+} gVaryingInfo;
+
+std::vector<Attribute> extractAttributes(JNIEnv* env, jobjectArray attributes) {
+    int size = env->GetArrayLength(attributes);
+    std::vector<Attribute> attVector;
+    attVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        jobject attribute = env->GetObjectArrayElement(attributes, i);
+        auto name = (jstring)env->GetObjectField(attribute, gAttributeInfo.name);
+        auto attName = ScopedUtfChars(env, name);
+        Attribute temp{Attribute::Type(env->GetIntField(attribute, gAttributeInfo.type)),
+                       static_cast<size_t>(env->GetIntField(attribute, gAttributeInfo.offset)),
+                       SkString(attName.c_str())};
+        attVector.push_back(std::move(temp));
+    }
+
+    return attVector;
+}
+
+std::vector<Varying> extractVaryings(JNIEnv* env, jobjectArray varyings) {
+    int size = env->GetArrayLength(varyings);
+    std::vector<Varying> varyVector;
+    varyVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        jobject varying = env->GetObjectArrayElement(varyings, i);
+        auto name = (jstring)env->GetObjectField(varying, gVaryingInfo.name);
+        auto varyName = ScopedUtfChars(env, name);
+        Varying temp{Varying::Type(env->GetIntField(varying, gVaryingInfo.type)),
+                     SkString(varyName.c_str())};
+        varyVector.push_back(std::move(temp));
+    }
+
+    return varyVector;
+}
+
+static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+                  jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader) {
+    auto attributes = extractAttributes(env, attributeArray);
+    auto varyings = extractVaryings(env, varyingArray);
+    auto skVertexShader = ScopedUtfChars(env, vertexShader);
+    auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+    auto meshSpec = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+                                              SkString(skVertexShader.c_str()),
+                                              SkString(skFragmentShader.c_str()))
+                            .specification;
+    return reinterpret_cast<jlong>(meshSpec.release());
+}
+
+static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+                        jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader,
+                        jlong colorSpace) {
+    auto attributes = extractAttributes(env, attributeArray);
+    auto varyings = extractVaryings(env, varyingArray);
+    auto skVertexShader = ScopedUtfChars(env, vertexShader);
+    auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+    auto meshSpec = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+                                              SkString(skVertexShader.c_str()),
+                                              SkString(skFragmentShader.c_str()),
+                                              GraphicsJNI::getNativeColorSpace(colorSpace))
+                            .specification;
+
+    return reinterpret_cast<jlong>(meshSpec.release());
+}
+
+static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArray,
+                           jint vertexStride, jobjectArray varyingArray, jstring vertexShader,
+                           jstring fragmentShader, jlong colorSpace, jint alphaType) {
+    auto attributes = extractAttributes(env, attributeArray);
+    auto varyings = extractVaryings(env, varyingArray);
+    auto skVertexShader = ScopedUtfChars(env, vertexShader);
+    auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+    auto meshSpec = SkMeshSpecification::Make(
+                            attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+                            SkString(skFragmentShader.c_str()),
+                            GraphicsJNI::getNativeColorSpace(colorSpace), SkAlphaType(alphaType))
+                            .specification;
+    return reinterpret_cast<jlong>(meshSpec.release());
+}
+
+static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) {
+    SkSafeUnref(meshSpec);
+}
+
+static jlong getMeshSpecificationFinalizer() {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
+}
+
+static const JNINativeMethod gMeshSpecificationMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshSpecificationFinalizer},
+        {"nativeMake",
+         "([Landroid/graphics/MeshSpecification$Attribute;I[Landroid/graphics/"
+         "MeshSpecification$Varying;"
+         "Ljava/lang/String;Ljava/lang/String;)J",
+         (void*)Make},
+        {"nativeMakeWithCS",
+         "([Landroid/graphics/MeshSpecification$Attribute;I"
+         "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;J)J",
+         (void*)MakeWithCS},
+        {"nativeMakeWithAlpha",
+         "([Landroid/graphics/MeshSpecification$Attribute;I"
+         "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;JI)J",
+         (void*)MakeWithAlpha}};
+
+int register_android_graphics_MeshSpecification(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/MeshSpecification",
+                                  gMeshSpecificationMethods, NELEM(gMeshSpecificationMethods));
+
+    gAttributeInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Attribute");
+    gAttributeInfo.type = env->GetFieldID(gAttributeInfo.clazz, "mType", "I");
+    gAttributeInfo.offset = env->GetFieldID(gAttributeInfo.clazz, "mOffset", "I");
+    gAttributeInfo.name = env->GetFieldID(gAttributeInfo.clazz, "mName", "Ljava/lang/String;");
+
+    gVaryingInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Varying");
+    gVaryingInfo.type = env->GetFieldID(gVaryingInfo.clazz, "mType", "I");
+    gVaryingInfo.name = env->GetFieldID(gVaryingInfo.clazz, "mName", "Ljava/lang/String;");
+    return 0;
+}
+
+}  // namespace android
\ No newline at end of file
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 529eddd..5acec79 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -101,6 +101,15 @@
     public static final String ACTION_FUSED_PROVIDER =
             "com.android.location.service.FusedLocationProvider";
 
+    /**
+     * The action the wrapping service should have in its intent filter to implement the
+     * {@link android.location.LocationManager#GPS_PROVIDER}.
+     *
+     * @hide
+     */
+    public static final String ACTION_GNSS_PROVIDER =
+            "android.location.provider.action.GNSS_PROVIDER";
+
     final String mTag;
     final @Nullable String mAttributionTag;
     final IBinder mBinder;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f3931df..9c5313a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7670,8 +7670,10 @@
      * or video calls. This method can be used by voice or video chat applications to select a
      * different audio device than the one selected by default by the platform.
      * <p>The device selection is expressed as an {@link AudioDeviceInfo} among devices returned by
-     * {@link #getAvailableCommunicationDevices()}.
-     * The selection is active as long as the requesting application process lives, until
+     * {@link #getAvailableCommunicationDevices()}. Note that only devices in a sink role
+     * (AKA output devices, see {@link AudioDeviceInfo#isSink()}) can be specified. The matching
+     * source device is selected automatically by the platform.
+     * <p>The selection is active as long as the requesting application process lives, until
      * {@link #clearCommunicationDevice} is called or until the device is disconnected.
      * It is therefore important for applications to clear the request when a call ends or the
      * the requesting activity or service is stopped or destroyed.
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 980f63b..59a0f7b 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -221,13 +221,6 @@
 
     /**
      * @hide
-     * Mute state used for the initial state and when API is accessed without permission.
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public static final int MUTED_BY_UNKNOWN = -1;
-    /**
-     * @hide
      * Flag used when muted by master volume.
      */
     @SystemApi
@@ -317,7 +310,7 @@
         mPlayerType = pic.mPlayerType;
         mClientUid = uid;
         mClientPid = pid;
-        mMutedState = MUTED_BY_UNKNOWN;
+        mMutedState = 0;
         mDeviceId = PLAYER_DEVICEID_INVALID;
         mPlayerState = PLAYER_STATE_IDLE;
         mPlayerAttr = pic.mAttributes;
@@ -366,7 +359,7 @@
         anonymCopy.mPlayerAttr = builder.build();
         anonymCopy.mDeviceId = in.mDeviceId;
         // anonymized data
-        anonymCopy.mMutedState = MUTED_BY_UNKNOWN;
+        anonymCopy.mMutedState = 0;
         anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
         anonymCopy.mClientUid = PLAYER_UPID_INVALID;
         anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -435,14 +428,13 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public boolean isMuted() {
-        return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN;
+        return mMutedState != 0;
     }
 
     /**
      * @hide
      * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
-     * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A
-     * value of 0 represents a player which is not muted.
+     * <br>A value of 0 corresponds to an unmuted player.
      * @return the mute state.
      */
     @SystemApi
@@ -602,11 +594,6 @@
     }
 
     private boolean isMuteAffectingActiveState() {
-        if (mMutedState == MUTED_BY_UNKNOWN) {
-            // mute state is not set, therefore it will not affect the active state
-            return false;
-        }
-
         return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
                 || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
                 || (mMutedState & MUTED_BY_APP_OPS) != 0;
@@ -726,9 +713,7 @@
                 "/").append(mClientPid).append(" state:").append(
                 toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
                 " sessionId:").append(mSessionId).append(" mutedState:");
-        if (mMutedState == MUTED_BY_UNKNOWN) {
-            apcToString.append("unknown ");
-        } else if (mMutedState == 0) {
+        if (mMutedState == 0) {
             apcToString.append("none ");
         } else {
             if ((mMutedState & MUTED_BY_MASTER) != 0) {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index d51f1e1..c2c752e 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1233,18 +1233,6 @@
         }
 
         /**
-         * Sets the tuner configuration for the {@code AudioTrack}.
-         *
-         * The {@link AudioTrack.TunerConfiguration} consists of parameters obtained from
-         * the Android TV tuner API which indicate the audio content stream id and the
-         * synchronization id for the {@code AudioTrack}.
-         *
-         * @param tunerConfiguration obtained by {@link AudioTrack.TunerConfiguration.Builder}.
-         * @return the same Builder instance.
-         * @hide
-         */
-
-        /**
          * @hide
          * Sets the {@link AudioTrack} call redirection mode.
          * Used when creating an AudioTrack to inject audio to call uplink path. The mode
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 9f3c3ff..bb31859 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -19,6 +19,7 @@
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 
 /**
@@ -30,6 +31,8 @@
     void notifySessionReleased(in RoutingSessionInfo session);
     void notifyDiscoveryPreferenceChanged(String packageName,
             in RouteDiscoveryPreference discoveryPreference);
+    void notifyRouteListingPreferenceChange(String packageName,
+            in @nullable RouteListingPreference routeListingPreference);
     void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
     void notifyRequestFailed(int requestId, int reason);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 742207b..bddda4a 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -23,6 +23,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouterClientState;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
@@ -57,6 +58,8 @@
     void unregisterRouter2(IMediaRouter2 router);
     void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
             in RouteDiscoveryPreference preference);
+    void setRouteListingPreference(IMediaRouter2 router,
+            in @nullable RouteListingPreference routeListingPreference);
     void setRouteVolumeWithRouter2(IMediaRouter2 router, in MediaRoute2Info route, int volume);
 
     void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 8a03afb..d6fe6825 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -86,8 +86,10 @@
      *
      * <p>
      * The format is one of the values from
-     * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
-     * formats and the planes is as follows:
+     * {@link android.graphics.ImageFormat ImageFormat},
+     * {@link android.graphics.PixelFormat PixelFormat}, or
+     * {@link android.hardware.HardwareBuffer HardwareBuffer}. The mapping between the
+     * formats and the planes is as follows (any formats not listed will have 1 plane):
      * </p>
      *
      * <table>
@@ -171,15 +173,18 @@
      * </tr>
      * <tr>
      *   <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td>
-     *   <td>1</td>
+     *   <td>3</td>
      *   <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
-     *     followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
-     *     little-endian value, with the lower 6 bits set to zero.
+     *     followed by a Wx(H/2) Cb and Cr planes. Each sample is represented by a 16-bit
+     *     little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be
+     *     a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane.
      *   </td>
      * </tr>
      * </table>
      *
      * @see android.graphics.ImageFormat
+     * @see android.graphics.PixelFormat
+     * @see android.hardware.HardwareBuffer
      */
     public abstract int getFormat();
 
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index a28ea32..d57a56a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -112,6 +112,10 @@
     @GuardedBy("mLock")
     final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @Nullable
+    private RouteListingPreference mRouteListingPreference;
+
     final RoutingController mSystemController;
 
     @GuardedBy("mLock")
@@ -461,6 +465,52 @@
         }
     }
 
+    /**
+     * Sets the {@link RouteListingPreference} of the app associated to this media router.
+     *
+     * <p>Use this method to inform the system UI of the routes that you would like to list for
+     * media routing, via the Output Switcher.
+     *
+     * <p>You should call this method before {@link #registerRouteCallback registering any route
+     * callbacks} and immediately after receiving any {@link RouteCallback#onRoutesUpdated route
+     * updates} in order to keep the system UI in a consistent state. You can also call this method
+     * at any other point to update the listing preference dynamically.
+     *
+     * <p>Notes:
+     *
+     * <ol>
+     *   <li>You should not include the ids of two or more routes with a match in their {@link
+     *       MediaRoute2Info#getDeduplicationIds() deduplication ids}. If you do, the system will
+     *       deduplicate them using its own criteria.
+     *   <li>You can use this method to rank routes in the output switcher, placing the more
+     *       important routes first. The system might override the proposed ranking.
+     *   <li>You can use this method to avoid listing routes using dynamic criteria. For example,
+     *       you can limit access to a specific type of device according to runtime criteria.
+     * </ol>
+     *
+     * @param routeListingPreference The {@link RouteListingPreference} for the system to use for
+     *     route listing. When null, the system uses its default listing criteria.
+     */
+    public void setRouteListingPreference(@Nullable RouteListingPreference routeListingPreference) {
+        synchronized (mLock) {
+            if (Objects.equals(mRouteListingPreference, routeListingPreference)) {
+                // Nothing changed. We return early to save a call to the system server.
+                return;
+            }
+            mRouteListingPreference = routeListingPreference;
+            try {
+                if (mStub == null) {
+                    MediaRouter2Stub stub = new MediaRouter2Stub();
+                    mMediaRouterService.registerRouter2(stub, mPackageName);
+                    mStub = stub;
+                }
+                mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
+            } catch (RemoteException ex) {
+                ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
     @GuardedBy("mLock")
     private boolean updateDiscoveryPreferenceIfNeededLocked() {
         RouteDiscoveryPreference newDiscoveryPreference = new RouteDiscoveryPreference.Builder(
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index e403e24..7786f61 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -93,6 +94,11 @@
     @NonNull
     final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
             new ConcurrentHashMap<>();
+    // TODO(b/241888071): Merge mDiscoveryPreferenceMap and mPackageToRouteListingPreferenceMap into
+    //     a single record object maintained by a single package-to-record map.
+    @NonNull
+    private final ConcurrentMap<String, RouteListingPreference>
+            mPackageToRouteListingPreferenceMap = new ConcurrentHashMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
     private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -355,6 +361,16 @@
     }
 
     /**
+     * Returns the {@link RouteListingPreference} of the app with the given {@code packageName}, or
+     * null if the app has not set any.
+     */
+    @Nullable
+    public RouteListingPreference getRouteListingPreference(@NonNull String packageName) {
+        Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
+        return mPackageToRouteListingPreferenceMap.get(packageName);
+    }
+
+    /**
      * Gets the system routing session for the given {@code packageName}.
      * Apps can select a route that is not the global route. (e.g. an app can select the device
      * route while BT route is available.)
@@ -686,6 +702,24 @@
         }
     }
 
+    private void updateRouteListingPreference(
+            @NonNull String packageName, @Nullable RouteListingPreference routeListingPreference) {
+        RouteListingPreference oldRouteListingPreference =
+                routeListingPreference == null
+                        ? mPackageToRouteListingPreferenceMap.remove(packageName)
+                        : mPackageToRouteListingPreferenceMap.put(
+                                packageName, routeListingPreference);
+        if (Objects.equals(oldRouteListingPreference, routeListingPreference)) {
+            return;
+        }
+        for (CallbackRecord record : mCallbackRecords) {
+            record.mExecutor.execute(
+                    () ->
+                            record.mCallback.onRouteListingPreferenceUpdated(
+                                    packageName, routeListingPreference));
+        }
+    }
+
     /**
      * Gets the unmodifiable list of selected routes for the session.
      */
@@ -971,6 +1005,19 @@
         }
 
         /**
+         * Called when the app with the given {@code packageName} updates its {@link
+         * MediaRouter2#setRouteListingPreference route listing preference}.
+         *
+         * @param packageName The package name of the app that changed its listing preference.
+         * @param routeListingPreference The new {@link RouteListingPreference} set by the app with
+         *     the given {@code packageName}. Maybe null if an app has unset its preference (by
+         *     passing null to {@link MediaRouter2#setRouteListingPreference}).
+         */
+        default void onRouteListingPreferenceUpdated(
+                @NonNull String packageName,
+                @Nullable RouteListingPreference routeListingPreference) {}
+
+        /**
          * Called when a previous request has failed.
          *
          * @param reason the reason that the request has failed. Can be one of followings:
@@ -1056,6 +1103,17 @@
         }
 
         @Override
+        public void notifyRouteListingPreferenceChange(
+                String packageName, @Nullable RouteListingPreference routeListingPreference) {
+            mHandler.sendMessage(
+                    obtainMessage(
+                            MediaRouter2Manager::updateRouteListingPreference,
+                            MediaRouter2Manager.this,
+                            packageName,
+                            routeListingPreference));
+        }
+
+        @Override
         public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
             mHandler.sendMessage(
                     obtainMessage(
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 538e64c..e78dc31 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -89,6 +89,7 @@
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .build();
+    private boolean mPreferBuiltinDevice;
     // playback properties, use synchronized with mPlaybackSettingsLock
     private boolean mIsLooping = false;
     private float mVolume = 1.0f;
@@ -157,6 +158,37 @@
     }
 
     /**
+     * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
+     * the one on which outgoing audio for SIM calls is played.
+     *
+     * @param audioManager the audio manage.
+     * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
+     *     none can be found.
+     */
+    private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
+        AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : deviceList) {
+            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the preferred device of the ringtong playback to the built-in device.
+     *
+     * @hide
+     */
+    public boolean preferBuiltinDevice(boolean enable) {
+        mPreferBuiltinDevice = enable;
+        if (mLocalPlayer == null) {
+            return true;
+        }
+        return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
+    }
+
+    /**
      * Creates a local media player for the ringtone using currently set attributes.
      * @return true if media player creation succeeded or is deferred,
      * false if it did not succeed and can't be tried remotely.
@@ -174,6 +206,8 @@
         try {
             mLocalPlayer.setDataSource(mContext, mUri);
             mLocalPlayer.setAudioAttributes(mAudioAttributes);
+            mLocalPlayer.setPreferredDevice(
+                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
             synchronized (mPlaybackSettingsLock) {
                 applyPlaybackProperties_sync();
             }
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/media/java/android/media/RouteListingPreference.aidl
similarity index 80%
copy from core/java/android/service/credentials/CreateCredentialResponse.aidl
copy to media/java/android/media/RouteListingPreference.aidl
index 73c9147..844dc8f 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/media/java/android/media/RouteListingPreference.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.credentials;
+package android.media;
 
-parcelable CreateCredentialResponse;
+parcelable RouteListingPreference;
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
new file mode 100644
index 0000000..26b190b
--- /dev/null
+++ b/media/java/android/media/RouteListingPreference.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Allows applications to customize the list of routes used for media routing (for example, in the
+ * System UI Output Switcher).
+ *
+ * @see MediaRouter2#setRouteListingPreference
+ */
+public final class RouteListingPreference implements Parcelable {
+
+    @NonNull
+    public static final Creator<RouteListingPreference> CREATOR =
+            new Creator<>() {
+                @Override
+                public RouteListingPreference createFromParcel(Parcel in) {
+                    return new RouteListingPreference(in);
+                }
+
+                @Override
+                public RouteListingPreference[] newArray(int size) {
+                    return new RouteListingPreference[size];
+                }
+            };
+
+    @NonNull private final List<Item> mItems;
+
+    /**
+     * Creates an instance with the given values.
+     *
+     * @param items See {@link #getItems()}.
+     */
+    public RouteListingPreference(@NonNull List<Item> items) {
+        mItems = List.copyOf(Objects.requireNonNull(items));
+    }
+
+    private RouteListingPreference(Parcel in) {
+        List<Item> items =
+                in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
+        mItems = List.copyOf(items);
+    }
+
+    /**
+     * Returns an unmodifiable list containing the items that the app wants to be listed for media
+     * routing.
+     */
+    @NonNull
+    public List<Item> getItems() {
+        return mItems;
+    }
+
+    // RouteListingPreference Parcelable implementation.
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelableList(mItems, flags);
+    }
+
+    // Equals and hashCode.
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof RouteListingPreference)) {
+            return false;
+        }
+        RouteListingPreference that = (RouteListingPreference) other;
+        return mItems.equals(that.mItems);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mItems);
+    }
+
+    // Internal classes.
+
+    /** Holds preference information for a specific route in a media routing listing. */
+    public static final class Item implements Parcelable {
+
+        @NonNull
+        public static final Creator<Item> CREATOR =
+                new Creator<>() {
+                    @Override
+                    public Item createFromParcel(Parcel in) {
+                        return new Item(in);
+                    }
+
+                    @Override
+                    public Item[] newArray(int size) {
+                        return new Item[size];
+                    }
+                };
+
+        @NonNull private final String mRouteId;
+
+        /**
+         * Creates an instance with the given value.
+         *
+         * @param routeId See {@link #getRouteId()}. Must not be empty.
+         */
+        public Item(@NonNull String routeId) {
+            Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
+            mRouteId = routeId;
+        }
+
+        private Item(Parcel in) {
+            String routeId = in.readString();
+            Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
+            mRouteId = routeId;
+        }
+
+        /** Returns the id of the route that corresponds to this route listing preference item. */
+        @NonNull
+        public String getRouteId() {
+            return mRouteId;
+        }
+
+        // Item Parcelable implementation.
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(mRouteId);
+        }
+
+        // Equals and hashCode.
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof Item)) {
+                return false;
+            }
+            Item item = (Item) other;
+            return mRouteId.equals(item.mRouteId);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mRouteId);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index fab63aa..7039a3e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -998,6 +998,7 @@
     private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
     private native int nativeStopScan();
     private native int nativeSetLnb(Lnb lnb);
+    private native boolean nativeIsLnaSupported();
     private native int nativeSetLna(boolean enable);
     private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
     private native Integer nativeGetAvSyncHwId(Filter filter);
@@ -1382,11 +1383,32 @@
     }
 
     /**
+     * Is Low Noise Amplifier (LNA) supported by the Tuner.
+     *
+     * <p>This API is only supported by Tuner HAL 3.0 or higher.
+     * Unsupported version would throw UnsupportedOperationException. Use
+     * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * @return {@code true} if supported, otherwise {@code false}.
+     * @throws UnsupportedOperationException if the Tuner HAL version is lower than 3.0
+     * @see android.media.tv.tuner.TunerVersionChecker#TUNER_VERSION_3_0
+     */
+    public boolean isLnaSupported() {
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_3_0, "isLnaSupported")) {
+            throw new UnsupportedOperationException("Tuner HAL version "
+                    + TunerVersionChecker.getTunerVersion() + " doesn't support this method.");
+        }
+        return nativeIsLnaSupported();
+    }
+
+    /**
      * Enable or Disable Low Noise Amplifier (LNA).
      *
      * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
      *
-     * @return result status of the operation.
+     * @return result status of the operation. {@link #RESULT_UNAVAILABLE} if the device doesn't
+     *         support LNA.
      */
     @Result
     public int setLnaEnabled(boolean enable) {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index a028c04..2afa4d1 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1819,6 +1819,13 @@
     return (int)result;
 }
 
+bool JTuner::isLnaSupported() {
+    if (sTunerClient == nullptr) {
+        return (int)Result::NOT_INITIALIZED;
+    }
+    return sTunerClient->isLnaSupported();
+}
+
 int JTuner::setLna(bool enable) {
     if (sTunerClient == nullptr) {
         return (int)Result::NOT_INITIALIZED;
@@ -3562,6 +3569,11 @@
     return tuner->setLnb(lnbClient);
 }
 
+static bool android_media_tv_Tuner_is_lna_supported(JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->isLnaSupported();
+}
+
 static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean enable) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->setLna(enable);
@@ -4810,6 +4822,7 @@
             (void *)android_media_tv_Tuner_scan },
     { "nativeStopScan", "()I", (void *)android_media_tv_Tuner_stop_scan },
     { "nativeSetLnb", "(Landroid/media/tv/tuner/Lnb;)I", (void *)android_media_tv_Tuner_set_lnb },
+    { "nativeIsLnaSupported", "()Z", (void *)android_media_tv_Tuner_is_lna_supported },
     { "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
     { "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;",
             (void *)android_media_tv_Tuner_get_frontend_status },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c74b2df..2b69e89 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -189,6 +189,7 @@
     int scan(const FrontendSettings& settings, FrontendScanType scanType);
     int stopScan();
     int setLnb(sp<LnbClient> lnbClient);
+    bool isLnaSupported();
     int setLna(bool enable);
     jobject openLnbByHandle(int handle);
     jobject openLnbByName(jstring name);
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 8515874..ab28fb4 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -210,4 +210,17 @@
     return -1;
 }
 
+bool TunerClient::isLnaSupported() {
+    if (mTunerService != nullptr) {
+        bool lnaSupported;
+        Status s = mTunerService->isLnaSupported(&lnaSupported);
+        if (!s.isOk()) {
+            return false;
+        }
+        return lnaSupported;
+    }
+
+    return false;
+}
+
 }  // namespace android
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 5410c1b..3f8b21c 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -148,6 +148,11 @@
      */
     int getMaxNumberOfFrontends(FrontendType frontendType);
 
+    /**
+     * Is Low Noise Amplifier (LNA) supported.
+     */
+    bool isLnaSupported();
+
 private:
     /**
      * An AIDL Tuner Service assigned at the first time the Tuner Client connects with
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 8594ba5..f1b1d79 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -34,6 +34,7 @@
 
 cc_defaults {
     name: "libandroid_defaults",
+    cpp_std: "gnu++20",
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 30d0c35..fe3132e 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -66,9 +66,6 @@
         return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight &&
                 mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes;
     }
-
-    AFont() = default;
-    AFont(const AFont&) = default;
 };
 
 struct FontHasher {
diff --git a/packages/CredentialManager/res/drawable/ic_other_sign_in.xml b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
new file mode 100644
index 0000000..8150197
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
@@ -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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="path"
+        android:pathData="M 20 19 L 12 19 L 12 21 L 20 21 C 21.1 21 22 20.1 22 19 L 22 5 C 22 3.9 21.1 3 20 3 L 12 3 L 12 5 L 20 5 L 20 19 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_1"
+        android:pathData="M 12 7 L 10.6 8.4 L 13.2 11 L 8.85 11 C 8.42 9.55 7.09 8.5 5.5 8.5 C 3.57 8.5 2 10.07 2 12 C 2 13.93 3.57 15.5 5.5 15.5 C 7.09 15.5 8.42 14.45 8.85 13 L 13.2 13 L 10.6 15.6 L 12 17 L 17 12 L 12 7 Z M 5.5 13.5 C 4.67 13.5 4 12.83 4 12 C 4 11.17 4.67 10.5 5.5 10.5 C 6.33 10.5 7 11.17 7 12 C 7 12.83 6.33 13.5 5.5 13.5 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_password.xml b/packages/CredentialManager/res/drawable/ic_password.xml
new file mode 100644
index 0000000..bf3056a
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_password.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="path"
+        android:pathData="M 8.71 10.29 C 8.52 10.1 8.28 10 8 10 L 7.75 10 L 7.75 8.75 C 7.75 7.98 7.48 7.33 6.95 6.8 C 6.42 6.27 5.77 6 5 6 C 4.23 6 3.58 6.27 3.05 6.8 C 2.52 7.33 2.25 7.98 2.25 8.75 L 2.25 10 L 2 10 C 1.72 10 1.48 10.1 1.29 10.29 C 1.1 10.48 1 10.72 1 11 L 1 16 C 1 16.28 1.1 16.52 1.29 16.71 C 1.48 16.9 1.72 17 2 17 L 8 17 C 8.28 17 8.52 16.9 8.71 16.71 C 8.9 16.52 9 16.28 9 16 L 9 11 C 9 10.72 8.9 10.48 8.71 10.29 Z M 6.25 10 L 3.75 10 L 3.75 8.75 C 3.75 8.4 3.87 8.1 4.11 7.86 C 4.35 7.62 4.65 7.5 5 7.5 C 5.35 7.5 5.65 7.62 5.89 7.86 C 6.13 8.1 6.25 8.4 6.25 8.75 L 6.25 10 Z M 10 14 L 23 14 L 23 16 L 10 16 Z M 21.5 9 C 21.102 9 20.721 9.158 20.439 9.439 C 20.158 9.721 20 10.102 20 10.5 C 20 10.898 20.158 11.279 20.439 11.561 C 20.721 11.842 21.102 12 21.5 12 C 21.898 12 22.279 11.842 22.561 11.561 C 22.842 11.279 23 10.898 23 10.5 C 23 10.102 22.842 9.721 22.561 9.439 C 22.279 9.158 21.898 9 21.5 9 Z M 16.5 9 C 16.102 9 15.721 9.158 15.439 9.439 C 15.158 9.721 15 10.102 15 10.5 C 15 10.898 15.158 11.279 15.439 11.561 C 15.721 11.842 16.102 12 16.5 12 C 16.898 12 17.279 11.842 17.561 11.561 C 17.842 11.279 18 10.898 18 10.5 C 18 10.102 17.842 9.721 17.561 9.439 C 17.279 9.158 16.898 9 16.5 9 Z M 11.5 9 C 11.102 9 10.721 9.158 10.439 9.439 C 10.158 9.721 10 10.102 10 10.5 C 10 10.898 10.158 11.279 10.439 11.561 C 10.721 11.842 11.102 12 11.5 12 C 11.898 12 12.279 11.842 12.561 11.561 C 12.842 11.279 13 10.898 13 10.5 C 13 10.102 12.842 9.721 12.561 9.439 C 12.279 9.158 11.898 9 11.5 9 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1ee2a26..8c9023c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -1,43 +1,77 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string name="app_name">CredentialManager</string>
+  <!-- The name of this application. Credential Manager is a service that centralizes and provides
+  access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] -->
+  <string name="app_name">Credential Manager</string>
+
+  <!-- Strings for the create flow. -->
+  <!-- Button label to close the dialog when the user does not want to create the credential. [CHAR LIMIT=40] -->
   <string name="string_cancel">Cancel</string>
+  <!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] -->
   <string name="string_continue">Continue</string>
-  <string name="string_more_options">More options</string>
+  <!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] -->
   <string name="string_create_in_another_place">Create in another place</string>
+  <!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] -->
   <string name="string_save_to_another_place">Save to another place</string>
+  <!-- This appears as a text button where users can click to use another device to create this credential. [CHAR LIMIT=80] -->
   <string name="string_use_another_device">Use another device</string>
+  <!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
   <string name="string_save_to_another_device">Save to another device</string>
-  <string name="string_no_thanks">No thanks</string>
+  <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
-  <string name="choose_provider_title">Choose where to <xliff:g id="createTypes">%1$s</xliff:g></string>
-  <!-- TODO: Change the wording after design is completed. -->
-  <string name="choose_provider_body">This password manager will store your passwords, passkeys, and other sign-in info to help you easily sign in. You can change where to save your sign-in info at any time.</string>
-  <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
-  <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
-  <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
-  <string name="choose_sign_in_title">Use saved sign in</string>
-  <string name="create_your_passkey">create your passkey</string>
+  <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
+  <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
+  <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
+  <string name="create_your_passkeys">create your passkeys</string>
   <string name="save_your_password">save your password</string>
   <string name="save_your_sign_in_info">save your sign-in info</string>
-  <string name="create_passkey_in">Create passkey in</string>
-  <string name="save_password_to">Save password to</string>
-  <string name="save_sign_in_to">Save sign-in to</string>
-  <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string>
-  <string name="set_as_default">Set as default</string>
-  <string name="use_once">Use once</string>
-  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName">%1$s</xliff:g> <xliff:g id="type">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName">%4$s</xliff:g></string>
-  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber">%2$s</xliff:g> passkeys</string>
-  <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords</string>
-  <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber">%1$s</xliff:g> passkeys</string>
+
+  <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
+  <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string>
+  <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] -->
   <string name="passkey">passkey</string>
   <string name="password">password</string>
   <string name="sign_ins">sign-ins</string>
-  <string name="another_device">Another device</string>
-  <string name="other_password_manager">Other password managers</string>
+
+  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_title">Create passkey in</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] -->
+  <string name="save_password_to_title">Save password to</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
+  <string name="save_sign_in_to_title">Save sign-in to</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
+  <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
   <!-- TODO: Check the wording here. -->
-  <string name="confirm_default_or_use_once_description">This password manager will store your passwords and passkeys to help you easily sign in.</string>
+  <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
+  <string name="use_provider_for_all_description">This password manager will store your passwords and passkeys to help you easily sign in.</string>
+  <!-- Button label to set the selected provider on the modal bottom sheet as default. [CHAR LIMIT=40] -->
+  <string name="set_as_default">Set as default</string>
+  <!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] -->
+  <string name="use_once">Use once</string>
+  <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
+  <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string>
+  <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string>
+  <!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] -->
+  <string name="passkey_before_subtitle">Passkey</string>
+  <!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] -->
+  <string name="another_device">Another device</string>
+  <!-- Appears as an option row title that users can choose to view other disabled providers. [CHAR LIMIT=80] -->
+  <string name="other_password_manager">Other password managers</string>
   <!-- Spoken content description of an element which will close the sheet when clicked. -->
   <string name="close_sheet">"Close sheet"</string>
   <!-- Spoken content description of the back arrow button. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 2bede9a..0cc1194 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -39,16 +39,17 @@
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
+import android.service.credentials.CredentialProviderService
 import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.RemoteInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest.Companion.createFrom
-import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 
 // Consider repo per screen, similar to view model?
@@ -66,7 +67,7 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testCreateRequestInfo()
+    ) ?: testCreatePasskeyRequestInfo()
 
     providerEnabledList = when (requestInfo.type) {
       RequestInfo.TYPE_CREATE ->
@@ -136,46 +137,29 @@
   }
 
   fun createCredentialInitialUiState(): CreateCredentialUiState {
+    val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
     val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
       // Handle runtime cast error
-      providerEnabledList as List<CreateCredentialProviderData>, context)
+      providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
     val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
       // Handle runtime cast error
       providerDisabledList as List<DisabledProviderData>, context)
-    var hasDefault = false
-    var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+    var defaultProvider: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
-      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
-    // TODO: covert from real requestInfo for create passkey
-    var requestDisplayInfo = RequestDisplayInfo(
-      "beckett-bakert@gmail.com",
-      "Elisa Beckett",
-      TYPE_PUBLIC_KEY_CREDENTIAL,
-      "tribank")
-    val createCredentialRequest = requestInfo.createCredentialRequest
-    val createCredentialRequestJetpack = createCredentialRequest?.let { createFrom(it) }
-    if (createCredentialRequestJetpack is CreatePasswordRequest) {
-      requestDisplayInfo = RequestDisplayInfo(
-        createCredentialRequestJetpack.id,
-        createCredentialRequestJetpack.password,
-        TYPE_PASSWORD_CREDENTIAL,
-        "tribank")
+      if (providerInfo.isDefault) {defaultProvider = providerInfo}
+      if (providerInfo.remoteEntry != null) {
+        remoteEntry = providerInfo.remoteEntry!!
+      }
     }
     return CreateCredentialUiState(
       enabledProviders = providerEnabledList,
       disabledProviders = providerDisabledList,
-      // TODO: Add the screen when defaultProvider has no createOption but
-      //  there's remoteInfo under other providers
-      if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
-        if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
-        {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
-      } else {CreateScreenState.CREATION_OPTION_SELECTION},
+      toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
       requestDisplayInfo,
       false,
-      if (hasDefault) {
-        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
-      } else null
+      toActiveEntry(defaultProvider, remoteEntry),
     )
   }
 
@@ -390,11 +374,12 @@
       intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
               or PendingIntent.FLAG_ONE_SHOT))
     val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
-      context.applicationInfo.packageName,
-      "PASSWORD",
-      toBundle("beckett-bakert@gmail.com", "password123")
+            context.applicationInfo.packageName,
+            TYPE_PASSWORD_CREDENTIAL,
+            toBundle("beckett-bakert@gmail.com", "password123")
     )
-    val fillInIntent = Intent().putExtra("create_request_params", createPasswordRequest)
+    val fillInIntent = Intent().putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
+            createPasswordRequest)
 
     val slice = Slice.Builder(
       Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
@@ -438,8 +423,42 @@
     )
   }
 
-  private fun testCreateRequestInfo(): RequestInfo {
-    val data = toBundle("beckett-bakert@gmail.com", "password123")
+  private fun testCreatePasskeyRequestInfo(): RequestInfo {
+    val request = CreatePublicKeyCredentialRequest("{\"extensions\": {\n" +
+            "                     \"webauthn.loc\": true\n" +
+            "                   },\n" +
+            "                   \"attestation\": \"direct\",\n" +
+            "                   \"challenge\": \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" +
+            "                   \"user\": {\n" +
+            "                     \"displayName\": \"testName\",\n" +
+            "                     \"name\": \"credManTesting@gmail.com\",\n" +
+            "                     \"id\": \"eD4o2KoXLpgegAtnM5cDhhUPvvk2\"\n" +
+            "                   },\n" +
+            "                   \"excludeCredentials\": [],\n" +
+            "                   \"rp\": {\n" +
+            "                     \"name\": \"Address Book\",\n" +
+            "                     \"id\": \"addressbook-c7876.uc.r.appspot.com\"\n" +
+            "                   },\n" +
+            "                   \"timeout\": 60000,\n" +
+            "                   \"pubKeyCredParams\": [\n" +
+            "                     {\n" +
+            "                       \"type\": \"public-key\",\n" +
+            "                       \"alg\": -7\n" +
+            "                     },\n" +
+            "                     {\n" +
+            "                       \"type\": \"public-key\",\n" +
+            "                       \"alg\": -257\n" +
+            "                     },\n" +
+            "                     {\n" +
+            "                       \"type\": \"public-key\",\n" +
+            "                       \"alg\": -37\n" +
+            "                     }\n" +
+            "                   ],\n" +
+            "                   \"authenticatorSelection\": {\n" +
+            "                     \"residentKey\": \"required\",\n" +
+            "                     \"requireResidentKey\": true\n" +
+            "                   }}")
+    val data = request.data
     return RequestInfo.newCreateRequestInfo(
       Binder(),
       CreateCredentialRequest(
@@ -451,6 +470,32 @@
     )
   }
 
+  private fun testCreatePasswordRequestInfo(): RequestInfo {
+    val data = toBundle("beckett-bakert@gmail.com", "password123")
+    return RequestInfo.newCreateRequestInfo(
+      Binder(),
+      CreateCredentialRequest(
+        TYPE_PASSWORD_CREDENTIAL,
+        data
+      ),
+      /*isFirstUsage=*/false,
+      "tribank"
+    )
+  }
+
+  private fun testCreateOtherCredentialRequestInfo(): RequestInfo {
+    val data = Bundle()
+    return RequestInfo.newCreateRequestInfo(
+      Binder(),
+      CreateCredentialRequest(
+        "other-sign-ins",
+        data
+      ),
+      /*isFirstUsage=*/false,
+      "tribank"
+    )
+  }
+
   private fun testGetRequestInfo(): RequestInfo {
     return RequestInfo.newGetRequestInfo(
       Binder(),
@@ -463,4 +508,38 @@
       "tribank.us"
     )
   }
+
+  private fun toCreateScreenState(
+    requestDisplayInfo: RequestDisplayInfo,
+    defaultProvider: EnabledProviderInfo?,
+    remoteEntry: RemoteInfo?,
+  ): CreateScreenState {
+    return if (
+      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
+    ){
+      CreateScreenState.EXTERNAL_ONLY_SELECTION
+    } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
+      if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
+        CreateScreenState.PASSKEY_INTRO
+      } else {
+        CreateScreenState.PROVIDER_SELECTION
+      }
+    } else {
+      CreateScreenState.CREATION_OPTION_SELECTION
+    }
+  }
+
+  private fun toActiveEntry(
+    defaultProvider: EnabledProviderInfo?,
+    remoteEntry: RemoteInfo?,
+  ): ActiveEntry? {
+    return if (
+      defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+    ) {
+      ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+    } else if (
+      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+      ActiveEntry(defaultProvider, remoteEntry)
+    } else null
+  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 2eb3284..b96f686 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -23,17 +23,23 @@
 import android.credentials.ui.GetCredentialProviderData
 import android.credentials.ui.CreateCredentialProviderData
 import android.credentials.ui.DisabledProviderData
+import android.credentials.ui.RequestInfo
 import android.graphics.drawable.Drawable
 import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.ActionEntryInfo
 import com.android.credentialmanager.getflow.AuthenticationEntryInfo
 import com.android.credentialmanager.getflow.CredentialEntryInfo
 import com.android.credentialmanager.getflow.ProviderInfo
 import com.android.credentialmanager.getflow.RemoteEntryInfo
+import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
 import com.android.credentialmanager.jetpack.provider.ActionUi
 import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
 import com.android.credentialmanager.jetpack.provider.SaveEntryUi
+import org.json.JSONObject
 
 /** Utility functions for converting CredentialManager data structures to or from UI formats. */
 class GetFlowUtils {
@@ -172,6 +178,7 @@
 
     fun toEnabledProviderList(
       providerDataList: List<CreateCredentialProviderData>,
+      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
     ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
       // TODO: get from the actual service info
@@ -194,7 +201,7 @@
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
           createOptions = toCreationOptionInfoList(
-            it.providerFlattenedComponentName, it.saveEntries, context),
+            it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
           isDefault = it.isDefaultProvider,
           remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
         )
@@ -219,9 +226,59 @@
       }
     }
 
+    fun toRequestDisplayInfo(
+      requestInfo: RequestInfo,
+      context: Context,
+    ): RequestDisplayInfo {
+      val createCredentialRequest = requestInfo.createCredentialRequest
+      val createCredentialRequestJetpack = createCredentialRequest?.let {
+        CreateCredentialRequest.createFrom(
+          it
+        )
+      }
+      when (createCredentialRequestJetpack) {
+        is CreatePasswordRequest -> {
+          return RequestDisplayInfo(
+            createCredentialRequestJetpack.id,
+            createCredentialRequestJetpack.password,
+            createCredentialRequestJetpack.type,
+            requestInfo.appPackageName,
+            context.getDrawable(R.drawable.ic_password)!!
+          )
+        }
+        is CreatePublicKeyCredentialRequest -> {
+          val requestJson = createCredentialRequestJetpack.requestJson
+          val json = JSONObject(requestJson)
+          var name = ""
+          var displayName = ""
+          if (json.has("user")) {
+            val user: JSONObject = json.getJSONObject("user")
+            name = user.getString("name")
+            displayName = user.getString("displayName")
+          }
+          return RequestDisplayInfo(
+            name,
+            displayName,
+            createCredentialRequestJetpack.type,
+            requestInfo.appPackageName,
+            context.getDrawable(R.drawable.ic_passkey)!!)
+        }
+        // TODO: correctly parsing for other sign-ins
+        else -> {
+          return RequestDisplayInfo(
+            "beckett-bakert@gmail.com",
+            "Elisa Beckett",
+            "other-sign-ins",
+            requestInfo.appPackageName,
+            context.getDrawable(R.drawable.ic_other_sign_in)!!)
+        }
+      }
+    }
+
     private fun toCreationOptionInfoList(
       providerId: String,
       creationEntries: List<Entry>,
+      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
     ): List<CreateOptionInfo> {
       return creationEntries.map {
@@ -236,7 +293,7 @@
           fillInIntent = it.frameworkExtrasIntent,
           userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
           profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
-            ?: context.getDrawable(R.drawable.ic_profile)!!,
+            ?: requestDisplayInfo.typeIcon,
           passwordCount = saveEntryUi.passwordCount ?: 0,
           passkeyCount = saveEntryUi.passkeyCount ?: 0,
           totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9f73aef..5552d05 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -60,7 +60,7 @@
     viewModel.onEntrySelected(it, providerActivityLauncher)
   }
   val confirmEntryCallback: () -> Unit = {
-    viewModel.onConfirmCreationSelected(providerActivityLauncher)
+    viewModel.onConfirmEntrySelected(providerActivityLauncher)
   }
   val state = rememberModalBottomSheetState(
     initialValue = ModalBottomSheetValue.Expanded,
@@ -108,9 +108,16 @@
           providerInfo = uiState.activeEntry?.activeProvider!!,
           onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
         )
+        CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+          requestDisplayInfo = uiState.requestDisplayInfo,
+          activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+          onOptionSelected = selectEntryCallback,
+          onConfirm = confirmEntryCallback,
+          onCancel = viewModel::onCancel,
+        )
       }
     },
-    scrimColor = MaterialTheme.colorScheme.scrim,
+    scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
     sheetShape = EntryShape.TopRoundedCorner,
   ) {}
   LaunchedEffect(state.currentValue) {
@@ -191,19 +198,18 @@
 ) {
   Card() {
     Column() {
-      // TODO: Change the icon for create passwords and sign-ins
       Icon(
-        painter = painterResource(R.drawable.ic_passkey),
+        bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
         contentDescription = null,
         tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
         modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-          .padding(top = 24.dp, bottom = 16.dp)
+          .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
       )
       Text(
         text = stringResource(
           R.string.choose_provider_title,
           when (requestDisplayInfo.type) {
-            TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkey)
+            TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkeys)
             TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_your_password)
             else -> stringResource(R.string.save_your_sign_in_info)
           },
@@ -274,6 +280,10 @@
           }
         }
       }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
       Row(
         horizontalArrangement = Arrangement.Start,
         modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
@@ -306,9 +316,9 @@
         title = {
           Text(
             text = when (requestDisplayInfo.type) {
-              TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in)
-              TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to)
-              else -> stringResource(R.string.save_sign_in_to)
+              TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in_title)
+              TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to_title)
+              else -> stringResource(R.string.save_sign_in_to_title)
             },
             style = MaterialTheme.typography.titleMedium
           )
@@ -399,7 +409,7 @@
         textAlign = TextAlign.Center,
       )
       Text(
-        text = stringResource(R.string.confirm_default_or_use_once_description),
+        text = stringResource(R.string.use_provider_for_all_description),
         style = MaterialTheme.typography.bodyLarge,
         modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
@@ -486,7 +496,7 @@
       ) {
         PrimaryCreateOptionRow(
           requestDisplayInfo = requestDisplayInfo,
-          createOptionInfo = createOptionInfo,
+          entryInfo = createOptionInfo,
           onOptionSelected = onOptionSelected
         )
       }
@@ -559,43 +569,129 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
+fun ExternalOnlySelectionCard(
+  requestDisplayInfo: RequestDisplayInfo,
+  activeRemoteEntry: EntryInfo,
+  onOptionSelected: (EntryInfo) -> Unit,
+  onConfirm: () -> Unit,
+  onCancel: () -> Unit,
+) {
+  Card() {
+    Column() {
+      Icon(
+        painter = painterResource(R.drawable.ic_other_devices),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+          .padding(all = 24.dp).size(32.dp)
+      )
+      Text(
+        text = stringResource(R.string.create_passkey_in_other_device_title),
+        style = MaterialTheme.typography.titleMedium,
+        modifier = Modifier.padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally),
+        textAlign = TextAlign.Center,
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = MaterialTheme.shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally),
+      ) {
+        PrimaryCreateOptionRow(
+          requestDisplayInfo = requestDisplayInfo,
+          entryInfo = activeRemoteEntry,
+          onOptionSelected = onOptionSelected
+        )
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.SpaceBetween,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(
+          stringResource(R.string.string_cancel),
+          onClick = onCancel
+        )
+        ConfirmButton(
+          stringResource(R.string.string_continue),
+          onClick = onConfirm
+        )
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
 fun PrimaryCreateOptionRow(
   requestDisplayInfo: RequestDisplayInfo,
-  createOptionInfo: CreateOptionInfo,
+  entryInfo: EntryInfo,
   onOptionSelected: (EntryInfo) -> Unit
 ) {
   Entry(
-    onClick = {onOptionSelected(createOptionInfo)},
+    onClick = {onOptionSelected(entryInfo)},
     icon = {
-      // TODO: Upload the other two types icons and change it according to request types
       Icon(
-        painter = painterResource(R.drawable.ic_passkey),
+        bitmap = if (entryInfo is CreateOptionInfo) {
+          entryInfo.profileIcon.toBitmap().asImageBitmap()
+        } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
         contentDescription = null,
         tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-        modifier = Modifier.padding(start = 18.dp)
+        modifier = Modifier.padding(start = 18.dp).size(32.dp)
       )
     },
     label = {
       Column() {
         // TODO: Add the function to hide/view password when the type is create password
-        if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
-          requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
-          Text(
-            text = requestDisplayInfo.title,
-            style = MaterialTheme.typography.titleLarge,
-            modifier = Modifier.padding(top = 16.dp)
-          )
-          Text(
-            text = requestDisplayInfo.subtitle,
-            style = MaterialTheme.typography.bodyMedium,
-            modifier = Modifier.padding(bottom = 16.dp)
-          )
-        } else {
-          Text(
-            text = requestDisplayInfo.title,
-            style = MaterialTheme.typography.titleLarge,
-            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
-          )
+        when (requestDisplayInfo.type) {
+            TYPE_PUBLIC_KEY_CREDENTIAL -> {
+              Text(
+                text = requestDisplayInfo.title,
+                style = MaterialTheme.typography.titleLarge,
+                modifier = Modifier.padding(top = 16.dp)
+              )
+              Text(
+                text = if (requestDisplayInfo.subtitle != null) {
+                  stringResource(
+                    R.string.passkey_before_subtitle) + " - " + requestDisplayInfo.subtitle
+                } else {stringResource(R.string.passkey_before_subtitle)},
+                style = MaterialTheme.typography.bodyMedium,
+                modifier = Modifier.padding(bottom = 16.dp)
+              )
+            }
+            TYPE_PASSWORD_CREDENTIAL -> {
+              Text(
+                text = requestDisplayInfo.title,
+                style = MaterialTheme.typography.titleLarge,
+                modifier = Modifier.padding(top = 16.dp)
+              )
+              Text(
+                // This subtitle would never be null for create password
+                text = requestDisplayInfo.subtitle ?: "",
+                style = MaterialTheme.typography.bodyMedium,
+                modifier = Modifier.padding(bottom = 16.dp)
+              )
+            }
+            else -> {
+              Text(
+                text = requestDisplayInfo.title,
+                style = MaterialTheme.typography.titleLarge,
+                modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
+              )
+            }
         }
       }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 0f685a1..393cf7d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -155,7 +155,7 @@
     }
   }
 
-  fun onConfirmCreationSelected(
+  fun onConfirmEntrySelected(
     launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
   ) {
     val selectedEntry = uiState.activeEntry?.activeEntryInfo
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 6dd6afb..9ac524a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -73,9 +73,10 @@
 
 data class RequestDisplayInfo(
   val title: String,
-  val subtitle: String,
+  val subtitle: String?,
   val type: String,
   val appDomainName: String,
+  val typeIcon: Drawable,
 )
 
 /**
@@ -94,4 +95,5 @@
   CREATION_OPTION_SELECTION,
   MORE_OPTIONS_SELECTION,
   MORE_OPTIONS_ROW_INTRO,
+  EXTERNAL_ONLY_SELECTION,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index db0c16c..720f231 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -61,6 +61,7 @@
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+import com.android.credentialmanager.ui.theme.EntryShape
 
 @Composable
 fun GetCredentialScreen(
@@ -94,8 +95,8 @@
         )
       }
     },
-    scrimColor = Color.Transparent,
-    sheetShape = MaterialTheme.shapes.medium,
+    scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+    sheetShape = EntryShape.TopRoundedCorner,
   ) {}
   LaunchedEffect(state.currentValue) {
     if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -167,7 +168,7 @@
         horizontalArrangement = Arrangement.Start,
         modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
       ) {
-        CancelButton(stringResource(R.string.string_no_thanks), onCancel)
+        CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
       }
       Divider(
         thickness = 18.dp,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
index 26d61f9..37a4f76 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
@@ -47,7 +47,7 @@
             return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
                 CreatePublicKeyCredentialRequest
                         .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
-                    CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+                    CreatePublicKeyCredentialRequest.createFrom(data)
                 CreatePublicKeyCredentialRequestPrivileged
                         .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
                     CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index e4bdab8..88c1036 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -28,7 +28,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
@@ -104,10 +103,9 @@
                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
         if (installerPackageNameFromIntent != null) {
             final String callingPkgName = getLaunchedFromPackage();
-            if (installerPackageNameFromIntent.length() >= SessionParams.MAX_PACKAGE_NAME_LENGTH
-                    || (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
+            if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
                     && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
-                    callingPkgName) != PackageManager.PERMISSION_GRANTED)) {
+                    callingPkgName) != PackageManager.PERMISSION_GRANTED) {
                 Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
                         + " is invalid. Remove it.");
                 EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index 0929706..bcedf50 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index a051ffe..37d6b42 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -36,13 +36,13 @@
         </activity>
 
         <provider
-            android:name="com.android.settingslib.spa.framework.SpaSearchProvider"
+            android:name="com.android.settingslib.spa.search.SpaSearchProvider"
             android:authorities="com.android.spa.gallery.search.provider"
             android:enabled="true"
             android:exported="false">
         </provider>
 
-        <provider android:name="com.android.settingslib.spa.framework.SpaSliceProvider"
+        <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
             android:authorities="com.android.spa.gallery.slice.provider"
             android:exported="true" >
             <intent-filter>
@@ -52,7 +52,7 @@
         </provider>
 
         <receiver
-            android:name="com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver"
+            android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
             android:exported="false">
         </receiver>
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 6d20c91..db49909 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,7 +17,6 @@
 package com.android.settingslib.spa.gallery
 
 import android.content.Context
-import com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver
 import com.android.settingslib.spa.framework.common.LocalLogger
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -29,6 +28,7 @@
 import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
+import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
@@ -39,6 +39,7 @@
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
+import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
 
 /**
  * Enum to define all SPP name here.
@@ -72,6 +73,7 @@
                 CategoryPageProvider,
                 ActionButtonPageProvider,
                 ProgressBarPageProvider,
+                LoadingBarPageProvider,
                 ChartPageProvider,
                 AlterDialogPageProvider,
             ),
@@ -81,9 +83,12 @@
         )
     }
 
+    override val logger = LocalLogger()
+
     override val browseActivityClass = GalleryMainActivity::class.java
     override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
+
+    // For debugging
     override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
     override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
-    override val logger = LocalLogger()
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
index 96e2498..decc292 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
@@ -46,7 +46,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
index a57df0f..063b61c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
@@ -50,7 +50,6 @@
     )
 
     fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
-        .setIsAllowSearch(true)
         .setUiLayoutFn {
             Preference(object : PreferenceModel {
                 override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 6d53dae..5d26b34 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -33,6 +33,7 @@
 import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
+import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
@@ -58,6 +59,7 @@
             CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             AlterDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 7958d11..42ac1ac 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -55,7 +55,6 @@
         entryList.add(
             createEntry(owner, EntryEnum.STRING_PARAM)
                 // Set attributes
-                .setIsAllowSearch(true)
                 .setIsSearchDataDynamic(true)
                 .setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
                 .setUiLayoutFn {
@@ -67,7 +66,6 @@
         entryList.add(
             createEntry(owner, EntryEnum.INT_PARAM)
                 // Set attributes
-                .setIsAllowSearch(true)
                 .setIsSearchDataDynamic(true)
                 .setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
                 .setUiLayoutFn {
@@ -90,8 +88,6 @@
             owner = createSettingsPage(arguments),
             displayName = "${name}_$stringParam",
         )
-            // Set attributes
-            .setIsAllowSearch(false)
             .setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
             .setUiLayoutFn {
                 // Set ui rendering
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
index 160e77b..7f21a4d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -134,7 +134,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index c903cfd..9f24ea9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
@@ -42,7 +43,7 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "Some Preference", owner)
-                .setIsAllowSearch(true)
+                .setSearchDataFn { EntrySearchData(title = "Some Preference") }
                 .setUiLayoutFn {
                     Preference(remember {
                         object : PreferenceModel {
@@ -58,7 +59,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index e10cf3a..ddf66aa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -72,7 +72,6 @@
 
      fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
new file mode 100644
index 0000000..4332a81
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.CircularLoadingBar
+import com.android.settingslib.spa.widget.ui.LinearLoadingBar
+
+private const val TITLE = "Sample LoadingBar"
+
+object LoadingBarPageProvider : SettingsPageProvider {
+    override val name = "LoadingBar"
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        var loading by remember { mutableStateOf(true) }
+        RegularScaffold(title = getTitle(arguments)) {
+            Button(
+                onClick = { loading = !loading },
+                modifier = Modifier.padding(start = 20.dp)
+            ) {
+                if (loading) {
+                    Text(text = "Stop")
+                } else {
+                    Text(text = "Resume")
+                }
+            }
+        }
+
+        LinearLoadingBar(isLoading = loading, yOffset = 104.dp)
+        CircularLoadingBar(isLoading = loading)
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun LoadingBarPagePreview() {
+    SettingsTheme {
+        LoadingBarPageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
index 9136b04..20d90dd 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -39,9 +38,7 @@
 import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
 import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-import com.android.settingslib.spa.widget.ui.CircularLoadingBar
 import com.android.settingslib.spa.widget.ui.CircularProgressBar
-import com.android.settingslib.spa.widget.ui.LinearLoadingBar
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample ProgressBar"
@@ -51,7 +48,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -66,18 +62,10 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        // Mocks a loading time of 2 seconds.
-        var loading by remember { mutableStateOf(true) }
-        LaunchedEffect(Unit) {
-            delay(2000)
-            loading = false
-        }
-
         RegularScaffold(title = getTitle(arguments)) {
             // Auto update the progress and finally jump tp 0.4f.
             var progress by remember { mutableStateOf(0f) }
             LaunchedEffect(Unit) {
-                delay(2000)
                 while (progress < 1f) {
                     delay(100)
                     progress += 0.01f
@@ -86,19 +74,11 @@
                 progress = 0.4f
             }
 
-            // Show as a placeholder for progress bar
             LargeProgressBar(progress)
-            // The remaining information only shows after loading complete.
-            if (!loading) {
-                SimpleProgressBar()
-                ProgressBarWithData()
-                CircularProgressBar(progress = progress, radius = 160f)
-            }
+            SimpleProgressBar()
+            ProgressBarWithData()
+            CircularProgressBar(progress = progress, radius = 160f)
         }
-
-        // Add loading bar examples, running for 2 seconds.
-        LinearLoadingBar(isLoading = loading, yOffset = 64.dp)
-        CircularLoadingBar(isLoading = loading)
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index cb58a95..c0d0abc 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -40,7 +40,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 73b34a5..a62ec7b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -48,7 +48,6 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create("Simple Slider", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SliderPreference(object : SliderPreferenceModel {
                         override val title = "Simple Slider"
@@ -58,7 +57,6 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Slider with icon", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SliderPreference(object : SliderPreferenceModel {
                         override val title = "Slider with icon"
@@ -72,7 +70,6 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Slider with changeable icon", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     val initValue = 0
                     var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
@@ -93,7 +90,6 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Slider with steps", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SliderPreference(object : SliderPreferenceModel {
                         override val title = "Slider with steps"
@@ -109,7 +105,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index f38a8d4..67e35dc 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -44,14 +44,12 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "MainSwitchPreference", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleMainSwitchPreference()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "MainSwitchPreference not changeable", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleNotChangeableMainSwitchPreference()
                 }.build()
@@ -62,7 +60,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index 61925a7..eddede7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -43,7 +43,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = owner)
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index ff89f2b..238204a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -36,6 +36,7 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.createIntent
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
@@ -87,16 +88,15 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             createEntry(EntryEnum.SIMPLE_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setMacro {
                     spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
                     SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = false) }
                 .build()
         )
         entryList.add(
             createEntry(EntryEnum.SUMMARY_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setMacro {
                     spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
                     SimplePreferenceMacro(
@@ -105,12 +105,12 @@
                         searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
                     )
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
                 .build()
         )
         entryList.add(singleLineSummaryEntry())
         entryList.add(
             createEntry(EntryEnum.DISABLED_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setHasMutableStatus(true)
                 .setMacro {
                     spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
@@ -126,7 +126,6 @@
         )
         entryList.add(
             createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setHasMutableStatus(true)
                 .setSearchDataFn {
                     EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
@@ -165,7 +164,6 @@
         )
         entryList.add(
             createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     val model = PreferencePageModel.create()
                     val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
@@ -179,7 +177,8 @@
                             }
                         }
                     )
-                }.setSliceDataFn { sliceUri, args ->
+                }
+                .setSliceDataFn { sliceUri, args ->
                     val createSliceImpl = { v: Int ->
                         createDemoActionSlice(
                             sliceUri = sliceUri,
@@ -204,7 +203,6 @@
         )
         entryList.add(
             createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     val model = PreferencePageModel.create()
                     val autoUpdaterSummary = remember { model.getAutoUpdaterSummary() }
@@ -251,7 +249,6 @@
     }
 
     private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
-        .setIsAllowSearch(true)
         .setUiLayoutFn {
             Preference(
                 model = object : PreferenceModel {
@@ -267,7 +264,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = owner)
-            .setIsAllowSearch(true)
             .setMacro {
                 spaLogger.message(TAG, "create macro for INJECT entry")
                 SimplePreferenceMacro(
@@ -276,7 +272,7 @@
                 )
             }
             .setSliceDataFn { sliceUri, _ ->
-                val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+                val intent = owner.createIntent()?.createBrowsePendingIntent()
                     ?: return@setSliceDataFn null
                 return@setSliceDataFn object : EntrySliceData() {
                     init {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 367766a..067911c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -17,6 +17,8 @@
 package com.android.settingslib.spa.gallery.preference
 
 import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.produceState
@@ -34,6 +36,7 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsIcon
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample SwitchPreference"
@@ -46,39 +49,40 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleSwitchPreference()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference with summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleSwitchPreferenceWithSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference with async summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleSwitchPreferenceWithAsyncSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference not changeable", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleNotChangeableSwitchPreference()
                 }.build()
         )
+        entryList.add(
+            SettingsEntryBuilder.create( "SwitchPreference with icon", owner)
+                .setUiLayoutFn {
+                    SampleSwitchPreferenceWithIcon()
+                }.build()
+        )
 
         return entryList
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -148,6 +152,21 @@
     })
 }
 
+@Composable
+private fun SampleSwitchPreferenceWithIcon() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+            override val icon = @Composable {
+                SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+            }
+        }
+    })
+}
+
 @Preview(showBackground = true)
 @Composable
 private fun SwitchPreferencePagePreview() {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index 22da99c..33e5e8d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -46,28 +46,24 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleTwoTargetSwitchPreference()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference with summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleTwoTargetSwitchPreferenceWithSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference with async summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleTwoTargetSwitchPreferenceWithAsyncSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference not changeable", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleNotChangeableTwoTargetSwitchPreference()
                 }.build()
@@ -78,7 +74,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index d87cbe8..cb58abf6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -39,7 +39,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index ec2f436..ba769d2 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -40,7 +40,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index eb7aaa7..3ea3b5c 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -33,6 +33,7 @@
         "androidx.compose.runtime_runtime-livedata",
         "androidx.compose.ui_ui-tooling-preview",
         "androidx.lifecycle_lifecycle-livedata-ktx",
+        "androidx.lifecycle_lifecycle-runtime-compose",
         "androidx.navigation_navigation-compose",
         "com.google.android.material_material",
         "lottie_compose",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 238268a..f7cbdae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -39,7 +39,10 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
 import com.android.settingslib.spa.slice.presenter.SliceDemo
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -158,14 +161,13 @@
             remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
         RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
             for (entry in allSliceEntry) {
-                SliceDemo(sliceUri = entry.createSliceUri(authority))
+                SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
             }
         }
     }
 
     @Composable
     fun OnePage(arguments: Bundle?) {
-        val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
         val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
         val pageWithEntry = entryRepository.getPageWithEntry(id)!!
@@ -176,8 +178,8 @@
             Text(text = "Entry size: ${pageWithEntry.entries.size}")
             Preference(model = object : PreferenceModel {
                 override val title = "open page"
-                override val enabled =
-                    page.isBrowsable(context, spaEnvironment.browseActivityClass).toState()
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    page.isBrowsable()).toState()
                 override val onClick = openPage(page)
             })
             EntryList(pageWithEntry.entries)
@@ -186,7 +188,6 @@
 
     @Composable
     fun OneEntry(arguments: Bundle?) {
-        val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
         val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
         val entry = entryRepository.getEntry(id)!!
@@ -194,9 +195,9 @@
         RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
             Preference(model = object : PreferenceModel {
                 override val title = "open entry"
-                override val enabled =
-                    entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass)
-                        .toState()
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    entry.containerPage().isBrowsable())
+                    .toState()
                 override val onClick = openEntry(entry)
             })
             Text(text = entryContent)
@@ -219,7 +220,7 @@
     private fun openPage(page: SettingsPage): (() -> Unit)? {
         val context = LocalContext.current
         val intent =
-            page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null
+            page.createIntent(SESSION_BROWSE) ?: return null
         val route = page.buildRoute()
         return {
             spaEnvironment.logger.message(
@@ -232,8 +233,7 @@
     @Composable
     private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
         val context = LocalContext.current
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+        val intent = entry.createIntent(SESSION_SEARCH)
             ?: return null
         val route = entry.containerPage().buildRoute()
         return {
@@ -245,18 +245,6 @@
     }
 }
 
-private fun SettingsEntry.createSliceUri(
-    authority: String?,
-    runtimeArguments: Bundle? = null
-): Uri {
-    if (authority == null) return Uri.EMPTY
-    return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
-        route = this.containerPage().buildRoute(),
-        entryId = this.id,
-        runtimeArguments = runtimeArguments,
-    ).build()
-}
-
 /**
  * A blank activity without any page.
  */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 3df7727..59ec985 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -32,6 +32,12 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.addUri
 import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
 
 private const val TAG = "DebugProvider"
 
@@ -116,9 +122,11 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
-            val command = pageWithEntry.page.createBrowseAdbCommand(
-                context,
-                spaEnvironment.browseActivityClass
+            val page = pageWithEntry.page
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                sessionName = SESSION_BROWSE
             )
             if (command != null) {
                 cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
@@ -131,8 +139,13 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            val command = entry.containerPage()
-                .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id)
+            val page = entry.containerPage()
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                entryId = entry.id,
+                sessionName = SESSION_SEARCH
+            )
             if (command != null) {
                 cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
             }
@@ -145,8 +158,7 @@
         val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val page = pageWithEntry.page
-            val intent =
-                page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent()
+            val intent = page.createIntent(SESSION_BROWSE) ?: Intent()
             cursor.newRow()
                 .add(ColumnEnum.PAGE_ID.id, page.id)
                 .add(ColumnEnum.PAGE_NAME.id, page.displayName)
@@ -162,17 +174,36 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            val intent = entry.containerPage()
-                .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
-                ?: Intent()
+            val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
             cursor.newRow()
                 .add(ColumnEnum.ENTRY_ID.id, entry.id)
                 .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
                 .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
                 .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
-                .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id,
-                    entryRepository.getEntryPathWithDisplayName(entry.id))
+                .add(
+                    ColumnEnum.ENTRY_HIERARCHY_PATH.id,
+                    entryRepository.getEntryPathWithDisplayName(entry.id)
+                )
         }
         return cursor
     }
 }
+
+private fun createBrowseAdbCommand(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null,
+): String? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val packageName = context.packageName
+    val activityName = browseActivityClass.name.replace(packageName, "")
+    val destinationParam =
+        if (destination != null) " -e $KEY_DESTINATION $destination" else ""
+    val highlightParam =
+        if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else ""
+    val sessionParam =
+        if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else ""
+    return "adb shell am start -n $packageName/$activityName" +
+        "$destinationParam$highlightParam$sessionParam"
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index c3c90ab..aa10cc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -16,20 +16,17 @@
 
 package com.android.settingslib.spa.framework
 
+import android.content.Intent
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.core.view.WindowCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
@@ -37,12 +34,17 @@
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
+import com.android.settingslib.spa.framework.util.getSessionName
 import com.android.settingslib.spa.framework.util.navRoute
 
 private const val TAG = "BrowseActivity"
@@ -74,86 +76,65 @@
 
         setContent {
             SettingsTheme {
-                MainContent()
+                val sppRepository by spaEnvironment.pageProviderRepository
+                BrowseContent(sppRepository, intent)
             }
         }
     }
+}
 
-    @Composable
-    private fun MainContent() {
-        val sppRepository by spaEnvironment.pageProviderRepository
-        val navController = rememberNavController()
-        val nullPage = SettingsPage.createNull()
-        CompositionLocalProvider(navController.localNavController()) {
-            NavHost(
-                navController = navController,
-                startDestination = nullPage.sppName,
-            ) {
-                composable(nullPage.sppName) {}
-                for (spp in sppRepository.getAllProviders()) {
-                    composable(
-                        route = spp.name + spp.parameter.navRoute(),
-                        arguments = spp.parameter,
-                    ) { navBackStackEntry ->
-                        PageLogger(remember(navBackStackEntry.arguments) {
-                            spp.createSettingsPage(arguments = navBackStackEntry.arguments)
-                        })
-
-                        spp.Page(navBackStackEntry.arguments)
-                    }
-                }
-            }
-            InitialDestinationNavigator()
-        }
-    }
-
-    @Composable
-    private fun PageLogger(settingsPage: SettingsPage) {
-        val lifecycleOwner = LocalLifecycleOwner.current
-        DisposableEffect(lifecycleOwner) {
-            val observer = LifecycleEventObserver { _, event ->
-                if (event == Lifecycle.Event.ON_START) {
-                    settingsPage.enterPage()
-                } else if (event == Lifecycle.Event.ON_STOP) {
-                    settingsPage.leavePage()
-                }
-            }
-
-            // Add the observer to the lifecycle
-            lifecycleOwner.lifecycle.addObserver(observer)
-
-            // When the effect leaves the Composition, remove the observer
-            onDispose {
-                lifecycleOwner.lifecycle.removeObserver(observer)
-            }
-        }
-    }
-
-    @Composable
-    private fun InitialDestinationNavigator() {
-        val sppRepository by spaEnvironment.pageProviderRepository
-        val destinationNavigated = rememberSaveable { mutableStateOf(false) }
-        if (destinationNavigated.value) return
-        destinationNavigated.value = true
+@VisibleForTesting
+@Composable
+fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
+    val navController = rememberNavController()
+    CompositionLocalProvider(navController.localNavController()) {
         val controller = LocalNavController.current as NavControllerWrapperImpl
-        LaunchedEffect(Unit) {
-            val destination =
-                intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPage()
-            val highlightEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
-            if (destination.isNotEmpty()) {
-                controller.highlightId = highlightEntryId
-                val navController = controller.navController
-                navController.navigate(destination) {
-                    popUpTo(navController.graph.findStartDestination().id) {
-                        inclusive = true
-                    }
-                }
+        controller.NavContent(sppRepository.getAllProviders())
+        controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage())
+    }
+}
+
+@Composable
+private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
+    val nullPage = SettingsPage.createNull()
+    NavHost(
+        navController = navController,
+        startDestination = nullPage.sppName,
+    ) {
+        composable(nullPage.sppName) {}
+        for (spp in allProvider) {
+            composable(
+                route = spp.name + spp.parameter.navRoute(),
+                arguments = spp.parameter,
+            ) { navBackStackEntry ->
+                spp.PageEvent(navBackStackEntry.arguments)
+                spp.Page(navBackStackEntry.arguments)
             }
         }
     }
+}
 
-    companion object {
-        const val KEY_DESTINATION = "spaActivityDestination"
-        const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+@Composable
+private fun NavControllerWrapperImpl.InitialDestination(
+    initialIntent: Intent?,
+    defaultDestination: String
+) {
+    val destinationNavigated = rememberSaveable { mutableStateOf(false) }
+    if (destinationNavigated.value) return
+    destinationNavigated.value = true
+
+    val initialDestination = initialIntent?.getDestination() ?: defaultDestination
+    if (initialDestination.isEmpty()) return
+    val initialEntryId = initialIntent?.getEntryId()
+    val sessionSourceName = initialIntent?.getSessionName()
+
+    LaunchedEffect(Unit) {
+        highlightId = initialEntryId
+        sessionName = sessionSourceName
+        navController.navigate(initialDestination) {
+            popUpTo(navController.graph.findStartDestination().id) {
+                inclusive = true
+            }
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
index 121c07f..61b46be 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.framework.common
 
 import android.content.UriMatcher
+import androidx.annotation.VisibleForTesting
 
 /**
  * Enum to define all column names in provider.
@@ -125,14 +126,17 @@
     ),
 }
 
-internal fun QueryEnum.getColumns(): Array<String> {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getColumns(): Array<String> {
     return columnNames.map { it.id }.toTypedArray()
 }
 
-internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getIndex(name: ColumnEnum): Int {
     return columnNames.indexOf(name)
 }
 
-internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
     uriMatcher.addURI(authority, queryPath, queryMatchCode)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 9ee7f9e..702c075 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -219,11 +219,6 @@
         return this
     }
 
-    fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
-        this.isAllowSearch = isAllowSearch
-        return this
-    }
-
     fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
         this.isSearchDataDynamic = isDynamic
         return this
@@ -251,6 +246,13 @@
 
     fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
         this.searchDataFn = fn
+        this.isAllowSearch = true
+        return this
+    }
+
+    fun clearSearchDataFn(): SettingsEntryBuilder {
+        this.searchDataFn = { null }
+        this.isAllowSearch = false
         return this
     }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index a372bbd..7a39b73 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -16,13 +16,8 @@
 
 package com.android.settingslib.spa.framework.common
 
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.util.isRuntimeParam
 import com.android.settingslib.spa.framework.util.navLink
 import com.android.settingslib.spa.framework.util.normalize
@@ -95,67 +90,21 @@
         return false
     }
 
-    fun enterPage() {
-        SpaEnvironmentFactory.instance.logger.event(
-            id,
-            LogEvent.PAGE_ENTER,
-            category = LogCategory.FRAMEWORK,
-            details = displayName,
-        )
-    }
-
-    fun leavePage() {
-        SpaEnvironmentFactory.instance.logger.event(
-            id,
-            LogEvent.PAGE_LEAVE,
-            category = LogCategory.FRAMEWORK,
-            details = displayName,
-        )
-    }
-
-    fun createBrowseIntent(entryId: String? = null): Intent? {
-        val context = SpaEnvironmentFactory.instance.appContext
-        val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
-        return createBrowseIntent(context, browseActivityClass, entryId)
-    }
-
-    fun createBrowseIntent(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): Intent? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        return Intent().setComponent(ComponentName(context!!, browseActivityClass!!))
-            .apply {
-                putExtra(BrowseActivity.KEY_DESTINATION, buildRoute())
-                if (entryId != null) {
-                    putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
-                }
-            }
-    }
-
-    fun createBrowseAdbCommand(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): String? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        val packageName = context!!.packageName
-        val activityName = browseActivityClass!!.name.replace(packageName, "")
-        val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}"
-        val highlightParam =
-            if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
-        return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
-    }
-
-    fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
-        return context != null &&
-            browseActivityClass != null &&
-            !isCreateBy(NULL_PAGE_NAME) &&
+    fun isBrowsable(): Boolean {
+        return !isCreateBy(NULL_PAGE_NAME) &&
             !hasRuntimeParam()
     }
 }
 
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+    return SettingsPage.create(
+        name = name,
+        displayName = displayName,
+        parameter = parameter,
+        arguments = arguments
+    )
+}
+
 fun String.toHashId(): String {
     return this.hashCode().toUInt().toString(36)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 60599d4..940005d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -51,12 +51,3 @@
         }
     }
 }
-
-fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
-    return SettingsPage.create(
-        name = name,
-        displayName = displayName,
-        parameter = parameter,
-        arguments = arguments
-    )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 945add4..6d0b810 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -70,11 +70,14 @@
     // In Robolectric test, applicationContext is not available. Use context as fallback.
     val appContext: Context = context.applicationContext ?: context
 
+    open val logger: SpaLogger = object : SpaLogger {}
+
     open val browseActivityClass: Class<out Activity>? = null
     open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
+
+    // Specify provider authorities for debugging purpose.
     open val searchProviderAuthorities: String? = null
     open val sliceProviderAuthorities: String? = null
-    open val logger: SpaLogger = object : SpaLogger {}
 
     // TODO: add other environment setup here.
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 382c498..eb2bffe 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -29,7 +29,10 @@
     fun navigateBack()
 
     val highlightEntryId: String?
-      get() = null
+        get() = null
+
+    val sessionSourceName: String?
+        get() = null
 }
 
 @Composable
@@ -63,6 +66,7 @@
     private val onBackPressedDispatcher: OnBackPressedDispatcher?,
 ) : NavControllerWrapper {
     var highlightId: String? = null
+    var sessionName: String? = null
 
     override fun navigate(route: String) {
         navController.navigate(route)
@@ -73,5 +77,8 @@
     }
 
     override val highlightEntryId: String?
-      get() = highlightId
+        get() = highlightId
+
+    override val sessionSourceName: String?
+        get() = sessionName
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
similarity index 97%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
index e26bdf7..90c44b5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.widget.util
+package com.android.settingslib.spa.framework.util
 
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.RepeatMode
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
similarity index 100%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
new file mode 100644
index 0000000..b9e4b78
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.spa.framework.util
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.LocalNavController
+
+@Composable
+internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+    val page = remember(arguments) { createSettingsPage(arguments) }
+    val lifecycleOwner = LocalLifecycleOwner.current
+    val navController = LocalNavController.current
+    DisposableEffect(lifecycleOwner) {
+        val observer = LifecycleEventObserver { _, event ->
+            val spaLogger = SpaEnvironmentFactory.instance.logger
+            if (event == Lifecycle.Event.ON_START) {
+                spaLogger.event(
+                    page.id,
+                    LogEvent.PAGE_ENTER,
+                    category = LogCategory.FRAMEWORK,
+                    details = navController.sessionSourceName ?: page.displayName,
+                )
+            } else if (event == Lifecycle.Event.ON_STOP) {
+                spaLogger.event(
+                    page.id,
+                    LogEvent.PAGE_LEAVE,
+                    category = LogCategory.FRAMEWORK,
+                    details = navController.sessionSourceName ?: page.displayName,
+                )
+            }
+        }
+
+        // Add the observer to the lifecycle
+        lifecycleOwner.lifecycle.addObserver(observer)
+
+        // When the effect leaves the Composition, remove the observer
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
new file mode 100644
index 0000000..2c3c2e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.spa.framework.util
+
+import android.content.ComponentName
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+const val SESSION_BROWSE = "browse"
+const val SESSION_SEARCH = "search"
+const val SESSION_SLICE = "slice"
+
+const val KEY_DESTINATION = "spaActivityDestination"
+const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+const val KEY_SESSION_SOURCE_NAME = "sessionSource"
+
+val SPA_INTENT_RESERVED_KEYS = listOf(
+    KEY_DESTINATION,
+    KEY_HIGHLIGHT_ENTRY,
+    KEY_SESSION_SOURCE_NAME
+)
+
+private fun createBaseIntent(): Intent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    return Intent().setComponent(ComponentName(context, browseActivityClass))
+}
+
+fun SettingsPage.createIntent(sessionName: String? = null): Intent? {
+    if (!isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = buildRoute(),
+        sessionName = sessionName
+    )
+}
+
+fun SettingsEntry.createIntent(sessionName: String? = null): Intent? {
+    val sp = containerPage()
+    if (!sp.isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = id,
+        sessionName = sessionName
+    )
+}
+
+fun Intent.appendSpaParams(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null
+): Intent {
+    return apply {
+        if (destination != null) putExtra(KEY_DESTINATION, destination)
+        if (entryId != null) putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+        if (sessionName != null) putExtra(KEY_SESSION_SOURCE_NAME, sessionName)
+    }
+}
+
+fun Intent.getDestination(): String? {
+    return getStringExtra(KEY_DESTINATION)
+}
+
+fun Intent.getEntryId(): String? {
+    return getStringExtra(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun Intent.getSessionName(): String? {
+    return getStringExtra(KEY_SESSION_SOURCE_NAME)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
similarity index 91%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 3689e4e..02aed1c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.search
 
 import android.content.ContentProvider
 import android.content.ContentValues
@@ -26,12 +26,15 @@
 import android.database.MatrixCursor
 import android.net.Uri
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import com.android.settingslib.spa.framework.common.ColumnEnum
 import com.android.settingslib.spa.framework.common.QueryEnum
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.addUri
 import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
 
 private const val TAG = "SpaSearchProvider"
 
@@ -115,7 +118,8 @@
         }
     }
 
-    private fun querySearchImmutableStatusData(): Cursor {
+    @VisibleForTesting
+    fun querySearchImmutableStatusData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -125,7 +129,8 @@
         return cursor
     }
 
-    private fun querySearchMutableStatusData(): Cursor {
+    @VisibleForTesting
+    fun querySearchMutableStatusData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -135,7 +140,8 @@
         return cursor
     }
 
-    private fun querySearchStaticData(): Cursor {
+    @VisibleForTesting
+    fun querySearchStaticData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -145,7 +151,8 @@
         return cursor
     }
 
-    private fun querySearchDynamicData(): Cursor {
+    @VisibleForTesting
+    fun querySearchDynamicData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -157,20 +164,19 @@
 
     private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
         val entryRepository by spaEnvironment.entryRepository
-        val browseActivityClass = spaEnvironment.browseActivityClass
 
         // Fetch search data. We can add runtime arguments later if necessary
         val searchData = entry.getSearchData() ?: return
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, browseActivityClass, entry.id)
-            ?: Intent()
+        val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
         cursor.newRow()
             .add(ColumnEnum.ENTRY_ID.id, entry.id)
             .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
             .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
             .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
-            .add(ColumnEnum.SEARCH_PATH.id,
-                entryRepository.getEntryPathWithTitle(entry.id, searchData.title))
+            .add(
+                ColumnEnum.SEARCH_PATH.id,
+                entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
+            )
     }
 
     private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
index 14855a8..7a4750d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.settingslib.spa.framework.common.EntrySliceData
 import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.util.getEntryId
 
 private const val TAG = "SliceDataRepository"
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index ff143ed..f362890 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -24,9 +24,15 @@
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.SESSION_SLICE
+import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
+import com.android.settingslib.spa.framework.util.appendSpaParams
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
 
 // Defines SliceUri, which contains special query parameters:
 //  -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -35,11 +41,6 @@
 // Use {entryId, runtimeParams} as the unique Id of this Slice.
 typealias SliceUri = Uri
 
-val RESERVED_KEYS = listOf(
-    KEY_DESTINATION,
-    KEY_HIGHLIGHT_ENTRY
-)
-
 fun SliceUri.getEntryId(): String? {
     return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
 }
@@ -51,7 +52,7 @@
 fun SliceUri.getRuntimeArguments(): Bundle {
     val params = Bundle()
     for (queryName in queryParameterNames) {
-        if (RESERVED_KEYS.contains(queryName)) continue
+        if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
         params.putString(queryName, getQueryParameter(queryName))
     }
     return params
@@ -63,12 +64,12 @@
     return "${entryId}_$params"
 }
 
-fun Uri.Builder.appendSliceParams(
-    route: String? = null,
+fun Uri.Builder.appendSpaParams(
+    destination: String? = null,
     entryId: String? = null,
     runtimeArguments: Bundle? = null
 ): Uri.Builder {
-    if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+    if (destination != null) appendQueryParameter(KEY_DESTINATION, destination)
     if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
     if (runtimeArguments != null) {
         for (key in runtimeArguments.keySet()) {
@@ -78,6 +79,20 @@
     return this
 }
 
+fun Uri.Builder.fromEntry(
+    entry: SettingsEntry,
+    authority: String?,
+    runtimeArguments: Bundle? = null
+): Uri.Builder {
+    if (authority == null) return this
+    val sp = entry.containerPage()
+    return scheme("content").authority(authority).appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = entry.id,
+        runtimeArguments = runtimeArguments
+    )
+}
+
 fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
     val context = SpaEnvironmentFactory.instance.appContext
     val sliceBroadcastClass =
@@ -97,8 +112,8 @@
 fun Intent.createBrowsePendingIntent(): PendingIntent? {
     val context = SpaEnvironmentFactory.instance.appContext
     val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
-    val destination = getStringExtra(KEY_DESTINATION) ?: return null
-    val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+    val destination = getDestination() ?: return null
+    val entryId = getEntryId()
     return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
 }
 
@@ -109,15 +124,12 @@
     entryId: String?
 ): PendingIntent {
     val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+        .appendSpaParams(destination, entryId, SESSION_SLICE)
         .apply {
             // Set both extra and data (which is a Uri) in Slice Intent:
             // 1) extra is used in SPA navigation framework
             // 2) data is used in Slice framework
-            putExtra(KEY_DESTINATION, destination)
-            if (entryId != null) {
-                putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
-            }
-            data = Uri.Builder().appendSliceParams(destination, entryId).build()
+            data = Uri.Builder().appendSpaParams(destination, entryId).build()
             flags = Intent.FLAG_ACTIVITY_NEW_TASK
         }
 
@@ -130,7 +142,7 @@
     entryId: String
 ): PendingIntent {
     val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
-        .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+        .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
     return PendingIntent.getBroadcast(
         context, 0 /* requestCode */, intent,
         PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
index 58131e6..39cb431 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.slice
 
 import android.content.BroadcastReceiver
 import android.content.Context
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
similarity index 98%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
index faa04fd..b809c0f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.slice
 
 import android.net.Uri
 import android.util.Log
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index db95e23..3e04b16 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -28,7 +28,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.EntryHighlight
 
 @Composable
 fun MainSwitchPreference(model: SwitchPreferenceModel) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 895edf7..b6099e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -24,12 +24,11 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import com.android.settingslib.spa.framework.common.EntryMacro
 import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.framework.util.wrapOnClickWithLog
 import com.android.settingslib.spa.widget.ui.createSettingsIcon
-import com.android.settingslib.spa.widget.util.EntryHighlight
 
 data class SimplePreferenceMacro(
     val title: String,
@@ -56,10 +55,6 @@
             keyword = searchKeywords
         )
     }
-
-    override fun getStatusData(): EntryStatusData {
-        return EntryStatusData(isDisabled = false)
-    }
 }
 
 /**
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
index 4ee2af0..7bca38f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
@@ -31,8 +31,8 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.widget.ui.SettingsSlider
-import com.android.settingslib.spa.widget.util.EntryHighlight
 
 /**
  * The widget model for [SliderPreference] widget.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index 2d60619..b67eb3d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -20,6 +20,8 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
@@ -31,9 +33,10 @@
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
+import com.android.settingslib.spa.widget.ui.SettingsIcon
 import com.android.settingslib.spa.widget.ui.SettingsSwitch
-import com.android.settingslib.spa.widget.util.EntryHighlight
 
 /**
  * The widget model for [SwitchPreference] widget.
@@ -51,6 +54,14 @@
         get() = stateOf("")
 
     /**
+     * The icon of this [Preference].
+     *
+     * Default is `null` which means no icon.
+     */
+    val icon: (@Composable () -> Unit)?
+        get() = null
+
+    /**
      * Indicates whether this [SwitchPreference] is checked.
      *
      * This can be `null` during the data loading before the data is available.
@@ -84,6 +95,7 @@
         InternalSwitchPreference(
             title = model.title,
             summary = model.summary,
+            icon = model.icon,
             checked = model.checked,
             changeable = model.changeable,
             onCheckedChange = model.onCheckedChange,
@@ -95,6 +107,7 @@
 internal fun InternalSwitchPreference(
     title: String,
     summary: State<String> = "".toState(),
+    icon: @Composable (() -> Unit)? = null,
     checked: State<Boolean?>,
     changeable: State<Boolean> = true.toState(),
     paddingStart: Dp = SettingsDimension.itemPaddingStart,
@@ -125,6 +138,7 @@
         paddingStart = paddingStart,
         paddingEnd = paddingEnd,
         paddingVertical = paddingVertical,
+        icon = icon,
     ) {
         SettingsSwitch(
             checked = checked,
@@ -152,6 +166,15 @@
                 checked = false.toState(),
                 onCheckedChange = {},
             )
+            InternalSwitchPreference(
+                title = "Use Dark theme",
+                summary = "Summary".toState(),
+                checked = true.toState(),
+                onCheckedChange = {},
+                icon = @Composable {
+                    SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+                },
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index fbfcaaa..63de2c8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.widget.preference
 
 import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.widget.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.widget.ui.SettingsSwitch
 
 @Composable
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
new file mode 100644
index 0000000..bd5884d
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.spa.framework
+
+import android.content.Context
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val WAIT_UNTIL_TIMEOUT = 1000L
+
+@RunWith(AndroidJUnit4::class)
+class BrowseActivityTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaLogger = SpaLoggerForTest()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
+
+    @Test
+    fun testBrowsePage() {
+        spaLogger.reset()
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        val sppRepository by spaEnvironment.pageProviderRepository
+        val sppHome = sppRepository.getProviderOrNull("SppHome")!!
+        val pageHome = sppHome.createSettingsPage()
+        val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
+        val pageLayer1 = sppLayer1.createSettingsPage()
+
+        composeTestRule.setContent { BrowseContent(sppRepository) }
+
+        composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
+        spaLogger.verifyPageEvent(pageHome.id, 1, 0)
+        spaLogger.verifyPageEvent(pageLayer1.id, 0, 0)
+
+        // click to layer1 page
+        composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick()
+        waitUntil(WAIT_UNTIL_TIMEOUT) {
+            composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null))
+                .fetchSemanticsNodes().size == 1
+        }
+        spaLogger.verifyPageEvent(pageHome.id, 1, 1)
+        spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
+    }
+}
+
+private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
+    Truth.assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+        .isEqualTo(entryCount)
+    Truth.assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+        .isEqualTo(leaveCount)
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index 9419161..c0b7464 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -19,6 +19,12 @@
 import android.content.Context
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
+import com.android.settingslib.spa.tests.testutils.SppLayer1
+import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -26,7 +32,8 @@
 @RunWith(AndroidJUnit4::class)
 class SettingsEntryRepositoryTest {
     private val context: Context = ApplicationProvider.getApplicationContext()
-    private val spaEnvironment = SpaEnvironmentForTest(context)
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
     private val entryRepository by spaEnvironment.entryRepository
 
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 2017d53..f98963c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -20,6 +20,8 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.core.os.bundleOf
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -64,6 +66,7 @@
         assertThat(entry.isAllowSearch).isFalse()
         assertThat(entry.isSearchDataDynamic).isFalse()
         assertThat(entry.hasMutableStatus).isFalse()
+        assertThat(entry.hasSliceSupport).isFalse()
     }
 
     @Test
@@ -121,12 +124,13 @@
     @Test
     fun testSetAttributes() {
         val owner = SettingsPage.create("mySpp")
-        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+        val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
             .setDisplayName("myEntryDisplay")
-            .setIsAllowSearch(true)
             .setIsSearchDataDynamic(false)
             .setHasMutableStatus(true)
-            .build()
+            .setSearchDataFn { null }
+            .setSliceDataFn { _, _ -> null }
+        val entry = entryBuilder.build()
         assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
         assertThat(entry.displayName).isEqualTo("myEntryDisplay")
         assertThat(entry.fromPage).isNull()
@@ -134,6 +138,10 @@
         assertThat(entry.isAllowSearch).isTrue()
         assertThat(entry.isSearchDataDynamic).isFalse()
         assertThat(entry.hasMutableStatus).isTrue()
+        assertThat(entry.hasSliceSupport).isTrue()
+
+        val entry2 = entryBuilder.clearSearchDataFn().build()
+        assertThat(entry2.isAllowSearch).isFalse()
     }
 
     @Test
@@ -150,6 +158,10 @@
 
         val rtArguments = bundleOf("rtParam" to "v2")
         composeTestRule.setContent { entry.UiLayout(rtArguments) }
+        assertThat(entry.isAllowSearch).isTrue()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.hasMutableStatus).isFalse()
+        assertThat(entry.hasSliceSupport).isFalse()
         val searchData = entry.getSearchData(rtArguments)
         val statusData = entry.getStatusData(rtArguments)
         assertThat(searchData?.title).isEqualTo("myTitle")
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 7097a5d..1f5de2d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -22,6 +22,8 @@
 import androidx.navigation.navArgument
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,8 +31,7 @@
 @RunWith(AndroidJUnit4::class)
 class SettingsPageTest {
     private val context: Context = ApplicationProvider.getApplicationContext()
-    private val spaLogger = SpaLoggerForTest()
-    private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+    private val spaEnvironment = SpaEnvironmentForTest(context)
 
     @Test
     fun testNullPage() {
@@ -42,9 +43,7 @@
         assertThat(page.isCreateBy("NULL")).isTrue()
         assertThat(page.isCreateBy("Spp")).isFalse()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
-        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
-        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+        assertThat(page.isBrowsable()).isFalse()
     }
 
     @Test
@@ -57,11 +56,7 @@
         assertThat(page.isCreateBy("NULL")).isFalse()
         assertThat(page.isCreateBy("mySpp")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
-        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
-        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
-            "-e spaActivityDestination mySpp"
-        )
+        assertThat(page.isBrowsable()).isTrue()
     }
 
     @Test
@@ -71,20 +66,20 @@
             "int_param" to 10,
         )
         val page = spaEnvironment.createPage("SppWithParam", arguments)
-        assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
-            navArgument("string_param") { type = NavType.StringType },
-            navArgument("int_param") { type = NavType.IntType },
-        ), arguments))
+        assertThat(page.id).isEqualTo(
+            getUniquePageId(
+                "SppWithParam", listOf(
+                    navArgument("string_param") { type = NavType.StringType },
+                    navArgument("int_param") { type = NavType.IntType },
+                ), arguments
+            )
+        )
         assertThat(page.sppName).isEqualTo("SppWithParam")
         assertThat(page.displayName).isEqualTo("SppWithParam")
         assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
         assertThat(page.isCreateBy("SppWithParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
-        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
-        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
-            "-e spaActivityDestination SppWithParam/myStr/10"
-        )
+        assertThat(page.isBrowsable()).isTrue()
     }
 
     @Test
@@ -95,33 +90,20 @@
             "rt_param" to "rtStr",
         )
         val page = spaEnvironment.createPage("SppWithRtParam", arguments)
-        assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
-            navArgument("string_param") { type = NavType.StringType },
-            navArgument("int_param") { type = NavType.IntType },
-            navArgument("rt_param") { type = NavType.StringType },
-        ), arguments))
+        assertThat(page.id).isEqualTo(
+            getUniquePageId(
+                "SppWithRtParam", listOf(
+                    navArgument("string_param") { type = NavType.StringType },
+                    navArgument("int_param") { type = NavType.IntType },
+                    navArgument("rt_param") { type = NavType.StringType },
+                ), arguments
+            )
+        )
         assertThat(page.sppName).isEqualTo("SppWithRtParam")
         assertThat(page.displayName).isEqualTo("SppWithRtParam")
         assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
         assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isTrue()
-        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
-        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
-        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
-    }
-
-    @Test
-    fun testPageEvent() {
-        spaLogger.reset()
-        SpaEnvironmentFactory.reset(spaEnvironment)
-        val page = spaEnvironment.createPage("SppHome")
-        page.enterPage()
-        page.leavePage()
-        page.enterPage()
-        assertThat(page.createBrowseIntent()).isNotNull()
-        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
-            .isEqualTo(2)
-        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
-            .isEqualTo(1)
+        assertThat(page.isBrowsable()).isFalse()
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
deleted file mode 100644
index b8731a3..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
+++ /dev/null
@@ -1,147 +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.settingslib.spa.framework.common
-
-import android.app.Activity
-import android.content.Context
-import android.os.Bundle
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.BrowseActivity
-
-class SpaLoggerForTest : SpaLogger {
-    data class MsgCountKey(val msg: String, val category: LogCategory)
-    data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
-
-    private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
-    private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
-
-    override fun message(tag: String, msg: String, category: LogCategory) {
-        val key = MsgCountKey("[$tag]$msg", category)
-        messageCount[key] = messageCount.getOrDefault(key, 0) + 1
-    }
-
-    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
-        val key = EventCountKey(id, event, category)
-        eventCount[key] = eventCount.getOrDefault(key, 0) + 1
-    }
-
-    fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
-        val key = MsgCountKey("[$tag]$msg", category)
-        return messageCount.getOrDefault(key, 0)
-    }
-
-    fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
-        val key = EventCountKey(id, event, category)
-        return eventCount.getOrDefault(key, 0)
-    }
-
-    fun reset() {
-        messageCount.clear()
-        eventCount.clear()
-    }
-}
-
-class MockActivity : BrowseActivity()
-
-object SppHome : SettingsPageProvider {
-    override val name = "SppHome"
-
-    override fun getTitle(arguments: Bundle?): String {
-        return "TitleHome"
-    }
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = this.createSettingsPage()
-        return listOf(
-            SppLayer1.buildInject().setLink(fromPage = owner).build(),
-        )
-    }
-}
-
-object SppLayer1 : SettingsPageProvider {
-    override val name = "SppLayer1"
-
-    override fun getTitle(arguments: Bundle?): String {
-        return "TitleLayer1"
-    }
-
-    fun buildInject(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(this.createSettingsPage())
-    }
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = this.createSettingsPage()
-        return listOf(
-            SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
-            SppLayer2.buildInject().setLink(fromPage = owner).build(),
-            SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
-        )
-    }
-}
-
-object SppLayer2 : SettingsPageProvider {
-    override val name = "SppLayer2"
-
-    fun buildInject(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(this.createSettingsPage())
-    }
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = this.createSettingsPage()
-        return listOf(
-            SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
-            SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
-        )
-    }
-}
-
-class SpaEnvironmentForTest(
-    context: Context,
-    override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
-    override val logger: SpaLogger = SpaLoggerForTest()
-) : SpaEnvironment(context) {
-
-    override val pageProviderRepository = lazy {
-        SettingsPageProviderRepository(
-            listOf(
-                SppHome, SppLayer1, SppLayer2,
-                object : SettingsPageProvider {
-                    override val name = "SppWithParam"
-                    override val parameter = listOf(
-                        navArgument("string_param") { type = NavType.StringType },
-                        navArgument("int_param") { type = NavType.IntType },
-                    )
-                },
-                object : SettingsPageProvider {
-                    override val name = "SppWithRtParam"
-                    override val parameter = listOf(
-                        navArgument("string_param") { type = NavType.StringType },
-                        navArgument("int_param") { type = NavType.IntType },
-                        navArgument("rt_param") { type = NavType.StringType },
-                    )
-                },
-            ),
-            listOf(SettingsPage.create("SppHome"))
-        )
-    }
-
-    fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
-        return pageProviderRepository.value
-            .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
-    }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
new file mode 100644
index 0000000..1854728
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.spa.framework.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaIntentTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
+    @Before
+    fun setEnvironment() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+    }
+
+    @Test
+    fun testCreateIntent() {
+        val nullPage = SettingsPage.createNull()
+        Truth.assertThat(nullPage.createIntent()).isNull()
+        Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
+            .isNull()
+
+        val page = spaEnvironment.createPage("SppHome")
+        val pageIntent = page.createIntent()
+        Truth.assertThat(pageIntent).isNotNull()
+        Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(pageIntent.getEntryId()).isNull()
+        Truth.assertThat(pageIntent.getSessionName()).isNull()
+
+        val entry = SettingsEntryBuilder.createInject(page).build()
+        val entryIntent = entry.createIntent(SESSION_SEARCH)
+        Truth.assertThat(entryIntent).isNotNull()
+        Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id)
+        Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
new file mode 100644
index 0000000..cdb0f3a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.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.settingslib.spa.search
+
+import android.content.Context
+import android.database.Cursor
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.ColumnEnum
+import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.common.getIndex
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppForSearch
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaSearchProviderTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
+    private val searchProvider = SpaSearchProvider()
+
+    @Test
+    fun testQuerySearchStatusData() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val pageOwner = spaEnvironment.createPage("SppForSearch")
+
+        val immutableStatus = searchProvider.querySearchImmutableStatusData()
+        Truth.assertThat(immutableStatus.count).isEqualTo(1)
+        immutableStatus.moveToFirst()
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+        )
+
+        val mutableStatus = searchProvider.querySearchMutableStatusData()
+        Truth.assertThat(mutableStatus.count).isEqualTo(2)
+        mutableStatus.moveToFirst()
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithMutableStatus")
+        )
+
+        mutableStatus.moveToNext()
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+        )
+    }
+
+    @Test
+    fun testQuerySearchIndexData() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val staticData = searchProvider.querySearchStaticData()
+        Truth.assertThat(staticData.count).isEqualTo(2)
+
+        val dynamicData = searchProvider.querySearchDynamicData()
+        Truth.assertThat(dynamicData.count).isEqualTo(2)
+    }
+}
+
+private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
+    Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
+}
+
+private fun SettingsPage.getEntryId(name: String): String {
+    return SettingsEntryBuilder.create(this, name).build().id
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
new file mode 100644
index 0000000..90e25f9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.spa.slice
+
+import android.content.Context
+import android.net.Uri
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
+import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsSliceDataRepositoryTest {
+    @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
+    private val sliceDataRepository by spaEnvironment.sliceDataRepository
+
+    @Test
+    fun getOrBuildSliceDataTest() {
+        // Slice empty
+        assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
+
+        // Slice supported
+        val page = SppLayer2.createSettingsPage()
+        val entryId = getUniqueEntryId("Layer2Entry1", page)
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
+        assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
+        assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
+        val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
+        assertThat(sliceData).isNotNull()
+        assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
+
+        // Slice unsupported
+        val entryId2 = getUniqueEntryId("Layer2Entry2", page)
+        val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
+        assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
+        assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
+        assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
+    }
+
+    @Test
+    fun getActiveSliceDataTest() {
+        val page = SppLayer2.createSettingsPage()
+        val entryId = getUniqueEntryId("Layer2Entry1", page)
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
+
+        // build slice data first
+        val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
+
+        // slice data is inactive
+        assertThat(sliceData!!.isActive()).isFalse()
+        assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
+
+        // slice data is active
+        val observer = Observer<Slice?> { }
+        sliceData.observeForever(observer)
+        assertThat(sliceData.isActive()).isTrue()
+        assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
+
+        // slice data is inactive again
+        sliceData.removeObserver(observer)
+        assertThat(sliceData.isActive()).isFalse()
+        assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
new file mode 100644
index 0000000..d1c4e51
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.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.settingslib.spa.slice
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SliceUtilTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
+    @Test
+    fun sliceUriTest() {
+        assertThat(Uri.EMPTY.getEntryId()).isNull()
+        assertThat(Uri.EMPTY.getDestination()).isNull()
+        assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
+        assertThat(Uri.EMPTY.getSliceId()).isNull()
+
+        // valid slice uri
+        val dest = "myRoute"
+        val entryId = "myEntry"
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
+        assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
+        assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
+        assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
+        assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
+
+        val sliceUriWithParams =
+            Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
+        assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
+        assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
+        assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
+        assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
+    }
+
+    @Test
+    fun createBroadcastPendingIntentTest() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        // Empty Slice Uri
+        assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
+
+        // Valid Slice Uri
+        val dest = "myRoute"
+        val entryId = "myEntry"
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
+        val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
+        assertThat(pendingIntent).isNotNull()
+        assertThat(pendingIntent!!.isBroadcast).isTrue()
+        assertThat(pendingIntent.isImmutable).isFalse()
+    }
+
+    @Test
+    fun createBrowsePendingIntentTest() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        // Empty Slice Uri
+        assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
+
+        // Empty Intent
+        assertThat(Intent().createBrowsePendingIntent()).isNull()
+
+        // Valid Slice Uri
+        val dest = "myRoute"
+        val entryId = "myEntry"
+        val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
+        val pendingIntent = sliceUri.createBrowsePendingIntent()
+        assertThat(pendingIntent).isNotNull()
+        assertThat(pendingIntent!!.isActivity).isTrue()
+        assertThat(pendingIntent.isImmutable).isTrue()
+
+        // Valid Intent
+        val intent = Intent().apply {
+            putExtra("spaActivityDestination", dest)
+            putExtra("highlightEntry", entryId)
+        }
+        val pendingIntent2 = intent.createBrowsePendingIntent()
+        assertThat(pendingIntent2).isNotNull()
+        assertThat(pendingIntent2!!.isActivity).isTrue()
+        assertThat(pendingIntent2.isImmutable).isTrue()
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..6385954
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.spa.tests.testutils
+
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.EntryStatusData
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
+import com.android.settingslib.spa.framework.common.SpaLogger
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
+
+class SpaLoggerForTest : SpaLogger {
+    data class MsgCountKey(val msg: String, val category: LogCategory)
+    data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
+
+    private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
+    private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
+
+    override fun message(tag: String, msg: String, category: LogCategory) {
+        val key = MsgCountKey("[$tag]$msg", category)
+        messageCount[key] = (messageCount[key] ?: 0) + 1
+    }
+
+    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+        val key = EventCountKey(id, event, category)
+        eventCount[key] = (eventCount[key] ?: 0) + 1
+    }
+
+    fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+        val key = MsgCountKey("[$tag]$msg", category)
+        return messageCount[key] ?: 0
+    }
+
+    fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+        val key = EventCountKey(id, event, category)
+        return eventCount[key] ?: 0
+    }
+
+    fun reset() {
+        messageCount.clear()
+        eventCount.clear()
+    }
+}
+
+class BlankActivity : BrowseActivity()
+class BlankSliceBroadcastReceiver : BroadcastReceiver() {
+    override fun onReceive(p0: Context?, p1: Intent?) {}
+}
+
+object SppHome : SettingsPageProvider {
+    override val name = "SppHome"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleHome"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SppLayer1.buildInject().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
+object SppLayer1 : SettingsPageProvider {
+    override val name = "SppLayer1"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleLayer1"
+    }
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+            .setMacro {
+                SimplePreferenceMacro(
+                    title = "SppHome to Layer1",
+                    clickRoute = name
+                )
+            }
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
+            SppLayer2.buildInject().setLink(fromPage = owner).build(),
+            SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
+        )
+    }
+}
+
+object SppLayer2 : SettingsPageProvider {
+    override val name = "SppLayer2"
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer2Entry1")
+                .setSliceDataFn { _, _ ->
+                    return@setSliceDataFn object : EntrySliceData() {
+                        init {
+                            postValue(null)
+                        }
+                    }
+                }
+                .build(),
+            SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+        )
+    }
+}
+
+object SppForSearch : SettingsPageProvider {
+    override val name = "SppForSearch"
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus")
+                .setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus")
+                .setHasMutableStatus(true)
+                .setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") }
+                .setStatusDataFn { EntryStatusData(isSwitchOff = true) }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus")
+                .setIsSearchDataDynamic(true)
+                .setHasMutableStatus(true)
+                .setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus")
+                .setIsSearchDataDynamic(true)
+                .setSearchDataFn {
+                    EntrySearchData(
+                        title = "SearchDynamicWithImmutableStatus",
+                        keyword = listOf("kw1", "kw2")
+                    )
+                }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
+                .build(),
+        )
+    }
+}
+
+class SpaEnvironmentForTest(
+    context: Context,
+    rootPages: List<SettingsPage> = emptyList(),
+    override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
+    override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
+        BlankSliceBroadcastReceiver::class.java,
+    override val logger: SpaLogger = object : SpaLogger {}
+) : SpaEnvironment(context) {
+
+    override val pageProviderRepository = lazy {
+        SettingsPageProviderRepository(
+            listOf(
+                SppHome, SppLayer1, SppLayer2,
+                SppForSearch,
+                object : SettingsPageProvider {
+                    override val name = "SppWithParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                    )
+                },
+                object : SettingsPageProvider {
+                    override val name = "SppWithRtParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                        navArgument("rt_param") { type = NavType.StringType },
+                    )
+                },
+            ),
+            rootPages
+        )
+    }
+
+    fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
+        return pageProviderRepository.value
+            .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 93f9afe..7e51fea 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.tests.testutils
 
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.toHashId
 import com.android.settingslib.spa.framework.util.normalize
 
 fun getUniquePageId(
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 0f618fa..48df569 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,6 +24,7 @@
     srcs: ["src/**/*.kt"],
 
     static_libs: [
+        "androidx.arch.core_core-runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
         "mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 58b4d42..be8df43 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -47,6 +47,7 @@
 }
 
 dependencies {
+    api "androidx.arch.core:core-runtime:2.1.0"
     api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
     api "com.google.truth:truth:1.1.3"
     api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
new file mode 100644
index 0000000..43c18d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
@@ -0,0 +1,55 @@
+/*
+ *  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.spa.testutils
+
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
+ * in LifecycleRegistry.
+
+ * This is a copy of androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be
+ * replaced once the dependency issue is solved.
+ */
+class InstantTaskExecutorRule : TestWatcher() {
+    override fun starting(description: Description) {
+        super.starting(description)
+        ArchTaskExecutor.getInstance().setDelegate(
+            object : TaskExecutor() {
+                override fun executeOnDiskIO(runnable: Runnable) {
+                    runnable.run()
+                }
+
+                override fun postToMainThread(runnable: Runnable) {
+                    runnable.run()
+                }
+
+                override fun isMainThread(): Boolean {
+                    return true
+                }
+            }
+        )
+    }
+
+    override fun finished(description: Description) {
+        super.finished(description)
+        ArchTaskExecutor.getInstance().setDelegate(null)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index bb1cd6e..76f6611 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spaprivileged.framework.common
 
+import android.app.ActivityManager
 import android.app.AlarmManager
 import android.app.AppOpsManager
 import android.app.admin.DevicePolicyManager
@@ -28,6 +29,9 @@
 import android.os.UserManager
 import android.permission.PermissionControllerManager
 
+/** The [ActivityManager] instance. */
+val Context.activityManager get() = getSystemService(ActivityManager::class.java)!!
+
 /** The [AlarmManager] instance. */
 val Context.alarmManager get() = getSystemService(AlarmManager::class.java)!!
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index b1adc9d..a618c3d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -20,27 +20,41 @@
 import android.content.Context
 import android.os.UserHandle
 import android.os.UserManager
-import androidx.lifecycle.liveData
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
 import com.android.settingslib.RestrictedLockUtilsInternal
 import com.android.settingslib.spaprivileged.R
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
 data class Restrictions(
     val userId: Int,
     val keys: List<String>,
 )
 
-sealed class RestrictedMode
+sealed interface RestrictedMode
 
-object NoRestricted : RestrictedMode()
+object NoRestricted : RestrictedMode
 
-object BaseUserRestricted : RestrictedMode()
+object BaseUserRestricted : RestrictedMode
 
-data class BlockedByAdmin(
-    val enterpriseRepository: EnterpriseRepository,
-    val enforcedAdmin: EnforcedAdmin,
-) : RestrictedMode() {
-    fun getSummary(checked: Boolean?): String = when (checked) {
+interface BlockedByAdmin : RestrictedMode {
+    fun getSummary(checked: Boolean?): String
+    fun sendShowAdminSupportDetailsIntent()
+}
+
+private data class BlockedByAdminImpl(
+    private val context: Context,
+    private val enforcedAdmin: EnforcedAdmin,
+) : BlockedByAdmin {
+    private val enterpriseRepository by lazy { EnterpriseRepository(context) }
+
+    override fun getSummary(checked: Boolean?) = when (checked) {
         true -> enterpriseRepository.getEnterpriseString(
             Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin
         )
@@ -49,18 +63,31 @@
         )
         else -> ""
     }
+
+    override fun sendShowAdminSupportDetailsIntent() {
+        RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin)
+    }
 }
 
-class RestrictionsProvider(
+interface RestrictionsProvider {
+    @Composable
+    fun restrictedModeState(): State<RestrictedMode?>
+}
+
+internal class RestrictionsProviderImpl(
     private val context: Context,
     private val restrictions: Restrictions,
-) {
+) : RestrictionsProvider {
     private val userManager by lazy { UserManager.get(context) }
-    private val enterpriseRepository by lazy { EnterpriseRepository(context) }
 
-    val restrictedMode = liveData {
+    private val restrictedMode = flow {
         emit(getRestrictedMode())
-    }
+    }.flowOn(Dispatchers.IO)
+
+    @OptIn(ExperimentalLifecycleComposeApi::class)
+    @Composable
+    override fun restrictedModeState() =
+        restrictedMode.collectAsStateWithLifecycle(initialValue = null)
 
     private fun getRestrictedMode(): RestrictedMode {
         for (key in restrictions.keys) {
@@ -71,12 +98,7 @@
         for (key in restrictions.keys) {
             RestrictedLockUtilsInternal
                 .checkIfRestrictionEnforced(context, key, restrictions.userId)
-                ?.let {
-                    return BlockedByAdmin(
-                        enterpriseRepository = enterpriseRepository,
-                        enforcedAdmin = it,
-                    )
-                }
+                ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) }
         }
         return NoRestricted
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 681eb1c..15766e1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -55,19 +55,22 @@
     val searchQuery: State<String>,
 )
 
+internal data class AppListInput<T : AppRecord>(
+    val config: AppListConfig,
+    val listModel: AppListModel<T>,
+    val state: AppListState,
+    val header: @Composable () -> Unit,
+    val appItem: @Composable AppListItemModel<T>.() -> Unit,
+    val bottomPadding: Dp,
+)
+
 /**
  * The template to render an App List.
  *
  * This UI element will take the remaining space on the screen to show the App List.
  */
 @Composable
-internal fun <T : AppRecord> AppList(
-    config: AppListConfig,
-    listModel: AppListModel<T>,
-    state: AppListState,
-    header: @Composable () -> Unit,
-    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
-    bottomPadding: Dp,
+internal fun <T : AppRecord> AppListInput<T>.AppList(
     appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
         loadAppListData(config, listModel, state)
     },
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index ac3f8ff..28bf832 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -35,16 +35,13 @@
 )
 
 @Composable
-fun <T : AppRecord> AppListItem(
-    itemModel: AppListItemModel<T>,
-    onClick: () -> Unit,
-) {
+fun <T : AppRecord> AppListItemModel<T>.AppListItem(onClick: () -> Unit) {
     Preference(remember {
         object : PreferenceModel {
-            override val title = itemModel.label
-            override val summary = itemModel.summary
+            override val title = label
+            override val summary = this@AppListItem.summary
             override val icon = @Composable {
-                AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize)
+                AppIcon(app = record.app, size = SettingsDimension.appIconItemSize)
             }
             override val onClick = onClick
         }
@@ -58,7 +55,6 @@
         val record = object : AppRecord {
             override val app = LocalContext.current.applicationInfo
         }
-        val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState())
-        AppListItem(itemModel) {}
+        AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {}
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index f371ce9..d452c74 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -47,7 +47,23 @@
     primaryUserOnly: Boolean = false,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
     header: @Composable () -> Unit = {},
-    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+    appItem: @Composable AppListItemModel<T>.() -> Unit,
+) {
+    AppListPageImpl(
+        title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem,
+    ) { it.AppList() }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListPageImpl(
+    title: String,
+    listModel: AppListModel<T>,
+    showInstantApps: Boolean = false,
+    primaryUserOnly: Boolean = false,
+    moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
+    header: @Composable () -> Unit = {},
+    appItem: @Composable AppListItemModel<T>.() -> Unit,
+    appList: @Composable (input: AppListInput<T>) -> Unit,
 ) {
     val showSystem = rememberSaveable { mutableStateOf(false) }
     SearchScaffold(
@@ -64,7 +80,7 @@
                 val options = remember { listModel.getSpinnerOptions() }
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
-                AppList(
+                val appListInput = AppListInput(
                     config = AppListConfig(
                         userId = userInfo.id,
                         showInstantApps = showInstantApps,
@@ -79,6 +95,7 @@
                     appItem = appItem,
                     bottomPadding = bottomPadding,
                 )
+                appList(appListInput)
             }
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index 5290bec..452971b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -9,8 +9,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 
 @Composable
-fun <T : AppRecord> AppListSwitchItem(
-    itemModel: AppListItemModel<T>,
+fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
     onClick: () -> Unit,
     checked: State<Boolean?>,
     changeable: State<Boolean>,
@@ -19,14 +18,14 @@
     TwoTargetSwitchPreference(
         model = remember {
             object : SwitchPreferenceModel {
-                override val title = itemModel.label
-                override val summary = itemModel.summary
+                override val title = label
+                override val summary = this@AppListSwitchItem.summary
                 override val checked = checked
                 override val changeable = changeable
                 override val onCheckedChange = onCheckedChange
             }
         },
-        icon = { AppIcon(itemModel.record.app, SettingsDimension.appIconItemSize) },
+        icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
         onClick = onClick,
     )
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index de5a4a2..8287693 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -66,7 +66,7 @@
         val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
-            SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
+            SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
         )
         return entryList
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index ec7d75e..00eb607 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
@@ -43,7 +42,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.flow.Flow
 
@@ -71,7 +70,6 @@
             entryList.add(
                 SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
                     .setLink(toPage = appInfoPage)
-                    .setIsAllowSearch(false)
                     .build()
             )
         }
@@ -93,12 +91,11 @@
         AppListPage(
             title = stringResource(listModel.pageTitleResId),
             listModel = internalListModel,
-        ) { itemModel ->
+        ) {
             AppListItem(
-                itemModel = itemModel,
                 onClick = TogglePermissionAppInfoPageProvider.navigator(
                     permissionType = permissionType,
-                    app = itemModel.record.app,
+                    app = record.app,
                 ),
             )
         }
@@ -121,7 +118,7 @@
                 parameter = PAGE_PARAMETER,
                 arguments = bundleOf(PERMISSION to permissionType)
             )
-            return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
+            return SettingsEntryBuilder.createInject(owner = appListPage)
                 .setUiLayoutFn {
                     val listModel = rememberContext(listModelSupplier)
                     Preference(
@@ -146,9 +143,7 @@
         listModel.filter(userIdFlow, recordListFlow)
 
     @Composable
-    override fun getSummary(option: Int, record: T): State<String> {
-        return getSummary(record)
-    }
+    override fun getSummary(option: Int, record: T) = getSummary(record)
 
     @Composable
     fun getSummary(record: T): State<String> {
@@ -157,27 +152,27 @@
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
             )
-            RestrictionsProvider(context, restrictions)
+            RestrictionsProviderImpl(context, restrictions)
         }
-        val restrictedMode = restrictionsProvider.restrictedMode.observeAsState()
+        val restrictedMode = restrictionsProvider.restrictedModeState()
         val allowed = listModel.isAllowed(record)
         return remember {
             derivedStateOf {
                 RestrictedSwitchPreference.getSummary(
                     context = context,
                     restrictedMode = restrictedMode.value,
-                    noRestrictedSummary = getNoRestrictedSummary(allowed),
+                    summaryIfNoRestricted = getSummaryIfNoRestricted(allowed),
                     checked = allowed,
                 ).value
             }
         }
     }
 
-    private fun getNoRestrictedSummary(allowed: State<Boolean?>) = derivedStateOf {
+    private fun getSummaryIfNoRestricted(allowed: State<Boolean?>) = derivedStateOf {
         when (allowed.value) {
             true -> context.getString(R.string.app_permission_summary_allowed)
             false -> context.getString(R.string.app_permission_summary_not_allowed)
-            else -> context.getString(R.string.summary_placeholder)
+            null -> context.getString(R.string.summary_placeholder)
         }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index 31fd3ad..a003da8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -22,12 +22,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.semantics.Role
-import com.android.settingslib.RestrictedLockUtils
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -38,32 +39,44 @@
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 
 @Composable
 fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
+    RestrictedSwitchPreferenceImpl(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@Composable
+internal fun RestrictedSwitchPreferenceImpl(
+    model: SwitchPreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+) {
     if (restrictions.keys.isEmpty()) {
         SwitchPreference(model)
         return
     }
     val context = LocalContext.current
-    val restrictionsProvider = remember { RestrictionsProvider(context, restrictions) }
-    val restrictedMode = restrictionsProvider.restrictedMode.observeAsState().value ?: return
+    val restrictionsProvider = remember(restrictions) {
+        restrictionsProviderFactory(context, restrictions)
+    }
+    val restrictedMode = restrictionsProvider.restrictedModeState().value
     val restrictedSwitchModel = remember(restrictedMode) {
         RestrictedSwitchPreferenceModel(context, model, restrictedMode)
     }
-    Box(remember { restrictedSwitchModel.getModifier() }) {
+    restrictedSwitchModel.RestrictionWrapper {
         SwitchPreference(restrictedSwitchModel)
     }
 }
 
-object RestrictedSwitchPreference {
+internal object RestrictedSwitchPreference {
     fun getSummary(
         context: Context,
         restrictedMode: RestrictedMode?,
-        noRestrictedSummary: State<String>,
+        summaryIfNoRestricted: State<String>,
         checked: State<Boolean?>,
     ): State<String> = when (restrictedMode) {
-        is NoRestricted -> noRestrictedSummary
+        is NoRestricted -> summaryIfNoRestricted
         is BaseUserRestricted -> stateOf(context.getString(R.string.disabled))
         is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
         null -> stateOf(context.getString(R.string.summary_placeholder))
@@ -71,43 +84,64 @@
 }
 
 private class RestrictedSwitchPreferenceModel(
-    private val context: Context,
+    context: Context,
     model: SwitchPreferenceModel,
-    private val restrictedMode: RestrictedMode,
+    private val restrictedMode: RestrictedMode?,
 ) : SwitchPreferenceModel {
     override val title = model.title
 
     override val summary = RestrictedSwitchPreference.getSummary(
         context = context,
         restrictedMode = restrictedMode,
-        noRestrictedSummary = model.summary,
+        summaryIfNoRestricted = model.summary,
         checked = model.checked,
     )
 
     override val checked = when (restrictedMode) {
+        null -> stateOf(null)
         is NoRestricted -> model.checked
         is BaseUserRestricted -> stateOf(false)
         is BlockedByAdmin -> model.checked
     }
 
     override val changeable = when (restrictedMode) {
+        null -> stateOf(false)
         is NoRestricted -> model.changeable
         is BaseUserRestricted -> stateOf(false)
         is BlockedByAdmin -> stateOf(false)
     }
 
     override val onCheckedChange = when (restrictedMode) {
+        null -> null
         is NoRestricted -> model.onCheckedChange
-        is BaseUserRestricted -> null
+        // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
+        // since changeable is false this will not be called.
+        is BaseUserRestricted -> model.onCheckedChange
+        // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
         is BlockedByAdmin -> null
     }
 
-    fun getModifier(): Modifier = when (restrictedMode) {
-        is BlockedByAdmin -> Modifier.clickable(role = Role.Switch) {
-            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
-                context, restrictedMode.enforcedAdmin
-            )
+    @Composable
+    fun RestrictionWrapper(content: @Composable () -> Unit) {
+        if (restrictedMode !is BlockedByAdmin) {
+            content()
+            return
         }
-        else -> Modifier
+        Box(
+            Modifier
+                .clickable(
+                    role = Role.Switch,
+                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                )
+                .semantics {
+                    this.toggleableState = ToggleableState(checked.value)
+                },
+        ) { content() }
+    }
+
+    private fun ToggleableState(value: Boolean?) = when (value) {
+        true -> ToggleableState.On
+        false -> ToggleableState.Off
+        null -> ToggleableState.Indeterminate
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
new file mode 100644
index 0000000..8c1421a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.template.scaffold
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+
+@Composable
+fun MoreOptionsScope.RestrictedMenuItem(
+    text: String,
+    restrictions: Restrictions,
+    onClick: () -> Unit,
+) {
+    RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
+}
+
+@Composable
+internal fun MoreOptionsScope.RestrictedMenuItemImpl(
+    text: String,
+    restrictions: Restrictions,
+    onClick: () -> Unit,
+    restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+) {
+    val context = LocalContext.current
+    val restrictionsProvider = remember(restrictions) {
+        restrictionsProviderFactory(context, restrictions)
+    }
+    val restrictedMode = restrictionsProvider.restrictedModeState().value
+    MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+        when (restrictedMode) {
+            is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
+            else -> onClick()
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
new file mode 100644
index 0000000..fb1e09a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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>
+    <!-- Test Permission title. [DO NOT TRANSLATE] -->
+    <string name="test_permission_title" translatable="false">Test Permission</string>
+
+    <!-- Test Permission switch title. [DO NOT TRANSLATE] -->
+    <string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
+
+    <!-- Test Permission footer. [DO NOT TRANSLATE] -->
+    <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index bc6925b..c4f2df2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -40,9 +40,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
-
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index b570815..65c547a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -39,8 +39,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListViewModelTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
index 4207490..4002655 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
@@ -40,8 +40,7 @@
 
 @RunWith(AndroidJUnit4::class)
 class PackageManagerExtTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
new file mode 100644
index 0000000..c3c96c6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun title_isDisplayed() {
+        setContent()
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun appListState_hasCorrectInitialState() {
+        val inputState by setContent()
+
+        val state = inputState!!.state
+        assertThat(state.showSystem.value).isFalse()
+        assertThat(state.option.value).isEqualTo(0)
+        assertThat(state.searchQuery.value).isEqualTo("")
+    }
+
+    @Test
+    fun canShowSystem() {
+        val inputState by setContent()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+        val state = inputState!!.state
+        assertThat(state.showSystem.value).isTrue()
+    }
+
+    @Test
+    fun afterShowSystem_displayHideSystem() {
+        setContent()
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun whenHasOptions_firstOptionDisplayed() {
+        val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+        composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+        composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
+        val state = inputState!!.state
+        assertThat(state.option.value).isEqualTo(0)
+    }
+
+    @Test
+    fun whenHasOptions_couldSwitchOption() {
+        val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+        composeTestRule.onNodeWithText(OPTION_0).performClick()
+        composeTestRule.onNodeWithText(OPTION_1).performClick()
+
+        composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
+        composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
+        val state = inputState!!.state
+        assertThat(state.option.value).isEqualTo(1)
+    }
+
+    private fun setContent(
+        options: List<String> = emptyList(),
+        header: @Composable () -> Unit = {},
+    ): State<AppListInput<TestAppRecord>?> {
+        val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
+        composeTestRule.setContent {
+            AppListPageImpl(
+                title = TITLE,
+                listModel = TestAppListModel(options),
+                header = header,
+                appItem = { AppListItem {} },
+                appList = { appListState.value = it },
+            )
+        }
+        return appListState
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val OPTION_0 = "Option 1"
+        const val OPTION_1 = "Option 2"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 9f20c78..df80dd4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -29,14 +29,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppEntry
 import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListData
-import com.android.settingslib.spaprivileged.model.app.AppListModel
-import com.android.settingslib.spaprivileged.model.app.AppRecord
-import kotlinx.coroutines.flow.Flow
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -92,21 +90,19 @@
         enableGrouping: Boolean = false,
     ) {
         composeTestRule.setContent {
-            AppList(
+            val appListInput = AppListInput(
                 config = AppListConfig(userId = USER_ID, showInstantApps = false),
-                listModel = TestAppListModel(enableGrouping),
+                listModel = TestAppListModel(enableGrouping = enableGrouping),
                 state = AppListState(
                     showSystem = false.toState(),
                     option = 0.toState(),
                     searchQuery = "".toState(),
                 ),
                 header = header,
-                appItem = { AppListItem(it) {} },
+                appItem = { AppListItem {} },
                 bottomPadding = 0.dp,
-                appListDataSupplier = {
-                    stateOf(AppListData(appEntries, option = 0))
-                }
             )
+            appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) }
         }
     }
 
@@ -137,25 +133,3 @@
         )
     }
 }
-
-private data class TestAppRecord(
-    override val app: ApplicationInfo,
-    val group: String? = null,
-) : AppRecord
-
-private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
-    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
-        appListFlow.asyncMapItem { TestAppRecord(it) }
-
-    @Composable
-    override fun getSummary(option: Int, record: TestAppRecord) = null
-
-    override fun filter(
-        userIdFlow: Flow<Int>,
-        option: Int,
-        recordListFlow: Flow<List<TestAppRecord>>,
-    ) = recordListFlow
-
-    override fun getGroupTitle(option: Int, record: TestAppRecord) =
-        if (enableGrouping) record.group else null
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index b3638c2..8e98d8c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -41,8 +41,7 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppStorageSizeTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
new file mode 100644
index 0000000..4bc612a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appListInjectEntry_titleDisplayed() {
+        val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
+            TestTogglePermissionAppListModel()
+        }.build()
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                entry.UiLayout()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun appListRoute() {
+        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    private companion object {
+        const val PERMISSION_TYPE = "test.PERMISSION"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
new file mode 100644
index 0000000..af3189f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appListInjectEntry_titleDisplayed() {
+        val entry = TestTogglePermissionAppListProvider.buildAppListInjectEntry().build()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                entry.UiLayout()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun appListRoute() {
+        val route = TestTogglePermissionAppListProvider.getAppListRoute()
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    @Test
+    fun togglePermissionAppListTemplate_createPageProviders() {
+        val togglePermissionAppListTemplate =
+            TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+        val createPageProviders = togglePermissionAppListTemplate.createPageProviders()
+
+        assertThat(createPageProviders).hasSize(2)
+        assertThat(createPageProviders.any { it is TogglePermissionAppListPageProvider }).isTrue()
+        assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
+    }
+}
+
+private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+    override val permissionType = "test.PERMISSION"
+    override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
new file mode 100644
index 0000000..7f57025
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.template.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedSwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private val switchPreferenceModel = object : SwitchPreferenceModel {
+        override val title = TITLE
+        override val checked = mutableStateOf(true)
+        override val onCheckedChange: (Boolean) -> Unit = { checked.value = it }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notToggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        composeTestRule.setContent {
+            RestrictedSwitchPreferenceImpl(switchPreferenceModel, restrictions) { _, _ ->
+                fakeRestrictionsProvider
+            }
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
new file mode 100644
index 0000000..983284c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.template.scaffold
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedMenuItemTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private var menuItemOnClickIsCalled = false
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(menuItemOnClickIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenNoRestricted_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(menuItemOnClickIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notClickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(menuItemOnClickIsCalled).isFalse()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_onClick_showAdminSupportDetails() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+        assertThat(menuItemOnClickIsCalled).isFalse()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        val fakeMoreOptionsScope = object : MoreOptionsScope {
+            override fun dismiss() {}
+        }
+        composeTestRule.setContent {
+            fakeMoreOptionsScope.RestrictedMenuItemImpl(
+                text = TEXT,
+                restrictions = restrictions,
+                onClick = { menuItemOnClickIsCalled = true },
+                restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+            )
+        }
+    }
+
+    private companion object {
+        const val TEXT = "Text"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
new file mode 100644
index 0000000..93fa17d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.tests.testutils
+
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+
+class FakeBlockedByAdmin : BlockedByAdmin {
+    var sendShowAdminSupportDetailsIntentIsCalled = false
+
+    override fun getSummary(checked: Boolean?) = SUMMARY
+
+    override fun sendShowAdminSupportDetailsIntent() {
+        sendShowAdminSupportDetailsIntentIsCalled = true
+    }
+
+    companion object {
+        const val SUMMARY = "Blocked by admin"
+    }
+}
+
+class FakeRestrictionsProvider : RestrictionsProvider {
+    var restrictedMode: RestrictedMode? = null
+
+    @Composable
+    override fun restrictedModeState() = stateOf(restrictedMode)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
new file mode 100644
index 0000000..d556487
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+
+data class TestAppRecord(
+    override val app: ApplicationInfo,
+    val group: String? = null,
+) : AppRecord
+
+class TestAppListModel(
+    private val options: List<String> = emptyList(),
+    private val enableGrouping: Boolean = false,
+) : AppListModel<TestAppRecord> {
+    override fun getSpinnerOptions() = options
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.asyncMapItem { TestAppRecord(it) }
+
+    @Composable
+    override fun getSummary(option: Int, record: TestAppRecord) = null
+
+    override fun filter(
+        userIdFlow: Flow<Int>,
+        option: Int,
+        recordListFlow: Flow<List<TestAppRecord>>,
+    ) = recordListFlow
+
+    override fun getGroupTitle(option: Int, record: TestAppRecord) =
+        if (enableGrouping) record.group else null
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
new file mode 100644
index 0000000..91a9c6b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import kotlinx.coroutines.flow.Flow
+
+class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> {
+    override val pageTitleResId = R.string.test_permission_title
+    override val switchTitleResId = R.string.test_permission_switch_title
+    override val footerResId = R.string.test_permission_footer
+
+    override fun transformItem(app: ApplicationInfo) = TestAppRecord(app = app)
+
+    override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<TestAppRecord>>) =
+        recordListFlow
+
+    @Composable
+    override fun isAllowed(record: TestAppRecord) = stateOf(null)
+
+    override fun isChangeable(record: TestAppRecord) = false
+
+    override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 3baef4b..e9c6aed 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2e0155b..caaa88d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1105,6 +1105,8 @@
     <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
     <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
     <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
+    <!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
+    <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string>
 
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 65c94ce..ca5f57d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1598,6 +1598,11 @@
          */
         public boolean isHomeApp;
 
+        /**
+         * Whether or not it's a cloned app .
+         */
+        public boolean isCloned;
+
         public String getNormalizedLabel() {
             if (normalizedLabel != null) {
                 return normalizedLabel;
@@ -1637,7 +1642,12 @@
                 ThreadUtils.postOnBackgroundThread(
                         () -> this.ensureLabelDescriptionLocked(context));
             }
-            this.showInPersonalTab = shouldShowInPersonalTab(context, info.uid);
+            UserManager um = UserManager.get(context);
+            this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+            UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
+            if (userInfo != null) {
+                this.isCloned = userInfo.isCloneProfile();
+            }
         }
 
         /**
@@ -1645,8 +1655,7 @@
          * {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set.
          */
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        boolean shouldShowInPersonalTab(Context context, int uid) {
-            UserManager userManager = UserManager.get(context);
+        boolean shouldShowInPersonalTab(UserManager userManager, int uid) {
             int userId = UserHandle.getUserId(uid);
 
             // Regardless of apk version, if the app belongs to the current user then return true.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 5c796af..a36cbc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -115,12 +115,24 @@
         }
 
         List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+        int resId = 0;
         for (LocalBluetoothProfile profile : profiles) {
-            int resId = profile.getDrawableResource(btClass);
-            if (resId != 0) {
-                return new Pair<>(getBluetoothDrawable(context, resId), null);
+            int profileResId = profile.getDrawableResource(btClass);
+            if (profileResId != 0) {
+                // The device should show hearing aid icon if it contains any hearing aid related
+                // profiles
+                if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
+                    return new Pair<>(getBluetoothDrawable(context, profileResId), null);
+                }
+                if (resId == 0) {
+                    resId = profileResId;
+                }
             }
         }
+        if (resId != 0) {
+            return new Pair<>(getBluetoothDrawable(context, resId), null);
+        }
+
         if (btClass != null) {
             if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
                 return new Pair<>(
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 9583a59..61c7fb9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -77,9 +77,7 @@
     private final LocalBluetoothProfileManager mProfileManager;
     private final Object mProfileLock = new Object();
     BluetoothDevice mDevice;
-    private int mDeviceSide;
-    private int mDeviceMode;
-    private long mHiSyncId;
+    private HearingAidInfo mHearingAidInfo;
     private int mGroupId;
 
     // Need this since there is no method for getting RSSI
@@ -160,7 +158,6 @@
         mProfileManager = profileManager;
         mDevice = device;
         fillData();
-        mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
         mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
         initDrawableCache();
         mUnpairing = false;
@@ -339,32 +336,34 @@
         connectDevice();
     }
 
-    public int getDeviceSide() {
-        return mDeviceSide;
+    public HearingAidInfo getHearingAidInfo() {
+        return mHearingAidInfo;
     }
 
-    public void setDeviceSide(int side) {
-        mDeviceSide = side;
+    public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+        mHearingAidInfo = hearingAidInfo;
+    }
+
+    /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device
+     */
+    public boolean isHearingAidDevice() {
+        return mHearingAidInfo != null;
+    }
+
+    public int getDeviceSide() {
+        return mHearingAidInfo != null
+                ? mHearingAidInfo.getSide() : HearingAidInfo.DeviceSide.SIDE_INVALID;
     }
 
     public int getDeviceMode() {
-        return mDeviceMode;
-    }
-
-    public void setDeviceMode(int mode) {
-        mDeviceMode = mode;
+        return mHearingAidInfo != null
+                ? mHearingAidInfo.getMode() : HearingAidInfo.DeviceMode.MODE_INVALID;
     }
 
     public long getHiSyncId() {
-        return mHiSyncId;
-    }
-
-    public void setHiSyncId(long id) {
-        mHiSyncId = id;
-    }
-
-    public boolean isHearingAidDevice() {
-        return mHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
+        return mHearingAidInfo != null
+                ? mHearingAidInfo.getHiSyncId() : BluetoothHearingAid.HI_SYNC_ID_INVALID;
     }
 
     /**
@@ -784,8 +783,6 @@
         ParcelUuid[] localUuids = new ParcelUuid[uuidsList.size()];
         uuidsList.toArray(localUuids);
 
-        if (localUuids == null) return false;
-
         /*
          * Now we know if the device supports PBAP, update permissions...
          */
@@ -1167,15 +1164,24 @@
 
                 // Try to show left/right information if can not get it from battery for hearing
                 // aids specifically.
-                if (mIsActiveDeviceHearingAid
+                boolean isActiveAshaHearingAid = mIsActiveDeviceHearingAid;
+                boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
+                        && isConnectedHapClientDevice();
+                if ((isActiveAshaHearingAid || isActiveLeAudioHearingAid)
                         && stringRes == R.string.bluetooth_active_no_battery_level) {
+                    final Set<CachedBluetoothDevice> memberDevices = getMemberDevice();
                     final CachedBluetoothDevice subDevice = getSubDevice();
-                    if (subDevice != null && subDevice.isConnected()) {
+                    if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
+                        stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
+                    } else if (subDevice != null && subDevice.isConnected()) {
                         stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
                     } else {
-                        if (mDeviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT) {
+                        int deviceSide = getDeviceSide();
+                        if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
+                            stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
+                        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
                             stringRes = R.string.bluetooth_hearing_aid_left_active;
-                        } else if (mDeviceSide == HearingAidProfile.DeviceSide.SIDE_RIGHT) {
+                        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
                             stringRes = R.string.bluetooth_hearing_aid_right_active;
                         } else {
                             stringRes = R.string.bluetooth_active_no_battery_level;
@@ -1371,15 +1377,41 @@
     }
 
     /**
-     * @return {@code true} if {@code cachedBluetoothDevice} is Hearing Aid device
+     * @return {@code true} if {@code cachedBluetoothDevice} is ASHA hearing aid device
      */
-    public boolean isConnectedHearingAidDevice() {
+    public boolean isConnectedAshaHearingAidDevice() {
         HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
         return hearingAidProfile != null && hearingAidProfile.getConnectionStatus(mDevice) ==
                 BluetoothProfile.STATE_CONNECTED;
     }
 
     /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is HAP device
+     */
+    public boolean isConnectedHapClientDevice() {
+        HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile();
+        return hapClientProfile != null && hapClientProfile.getConnectionStatus(mDevice)
+                == BluetoothProfile.STATE_CONNECTED;
+    }
+
+    /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio hearing aid device
+     */
+    public boolean isConnectedLeAudioHearingAidDevice() {
+        return isConnectedHapClientDevice() && isConnectedLeAudioDevice();
+    }
+
+    /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device
+     *
+     * The device may be an ASHA hearing aid that supports {@link HearingAidProfile} or a LeAudio
+     * hearing aid that supports {@link HapClientProfile} and {@link LeAudioProfile}.
+     */
+    public boolean isConnectedHearingAidDevice() {
+        return isConnectedAshaHearingAidDevice() || isConnectedLeAudioHearingAidDevice();
+    }
+
+    /**
      * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio device
      */
     public boolean isConnectedLeAudioDevice() {
@@ -1407,17 +1439,17 @@
         BluetoothDevice tmpDevice = mDevice;
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
-        final int tmpDeviceSide = mDeviceSide;
+        final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
         // Set main device from sub device
         mDevice = mSubDevice.mDevice;
         mRssi = mSubDevice.mRssi;
         mJustDiscovered = mSubDevice.mJustDiscovered;
-        mDeviceSide = mSubDevice.mDeviceSide;
+        mHearingAidInfo = mSubDevice.mHearingAidInfo;
         // Set sub device from backup
         mSubDevice.mDevice = tmpDevice;
         mSubDevice.mRssi = tmpRssi;
         mSubDevice.mJustDiscovered = tmpJustDiscovered;
-        mSubDevice.mDeviceSide = tmpDeviceSide;
+        mSubDevice.mHearingAidInfo = tmpHearingAidInfo;
         fetchActiveDevices();
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index cf4e1ee..ebfec0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -27,7 +27,7 @@
 import java.util.Set;
 
 /**
- * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices.
+ * HearingAidDeviceManager manages the set of remote HearingAid(ASHA) Bluetooth devices.
  */
 public class HearingAidDeviceManager {
     private static final String TAG = "HearingAidDeviceManager";
@@ -44,12 +44,12 @@
     void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
         long hiSyncId = getHiSyncId(newDevice.getDevice());
         if (isValidHiSyncId(hiSyncId)) {
-            // Once hiSyncId is valid, assign hiSyncId
-            newDevice.setHiSyncId(hiSyncId);
-            final int side = getDeviceSide(newDevice.getDevice());
-            final int mode = getDeviceMode(newDevice.getDevice());
-            newDevice.setDeviceSide(side);
-            newDevice.setDeviceMode(mode);
+            // Once hiSyncId is valid, assign hearing aid info
+            final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                    .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
+                    .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
+                    .setHiSyncId(hiSyncId);
+            newDevice.setHearingAidInfo(infoBuilder.build());
         }
     }
 
@@ -123,12 +123,14 @@
                 final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
                 // Do nothing if there is no HiSyncId on Bluetooth device
                 if (isValidHiSyncId(newHiSyncId)) {
-                    cachedDevice.setHiSyncId(newHiSyncId);
+                    // Once hiSyncId is valid, assign hearing aid info
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
+                            .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
+                            .setHiSyncId(newHiSyncId);
+                    cachedDevice.setHearingAidInfo(infoBuilder.build());
+
                     newSyncIdSet.add(newHiSyncId);
-                    final int side = getDeviceSide(cachedDevice.getDevice());
-                    final int mode = getDeviceMode(cachedDevice.getDevice());
-                    cachedDevice.setDeviceSide(side);
-                    cachedDevice.setDeviceMode(mode);
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
new file mode 100644
index 0000000..ef08c92
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
@@ -0,0 +1,263 @@
+/*
+ * 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.bluetooth;
+
+import android.annotation.IntDef;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
+import android.util.SparseIntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** Hearing aids information and constants that shared within hearing aids related profiles */
+public class HearingAidInfo {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DeviceSide.SIDE_INVALID,
+            DeviceSide.SIDE_LEFT,
+            DeviceSide.SIDE_RIGHT,
+            DeviceSide.SIDE_LEFT_AND_RIGHT,
+    })
+
+    /** Side definition for hearing aids. */
+    public @interface DeviceSide {
+        int SIDE_INVALID = -1;
+        int SIDE_LEFT = 0;
+        int SIDE_RIGHT = 1;
+        int SIDE_LEFT_AND_RIGHT = 2;
+    }
+
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @IntDef({
+            DeviceMode.MODE_INVALID,
+            DeviceMode.MODE_MONAURAL,
+            DeviceMode.MODE_BINAURAL,
+            DeviceMode.MODE_BANDED,
+    })
+
+    /** Mode definition for hearing aids. */
+    public @interface DeviceMode {
+        int MODE_INVALID = -1;
+        int MODE_MONAURAL = 0;
+        int MODE_BINAURAL = 1;
+        int MODE_BANDED = 2;
+    }
+
+    private final int mSide;
+    private final int mMode;
+    private final long mHiSyncId;
+
+    private HearingAidInfo(int side, int mode, long hiSyncId) {
+        mSide = side;
+        mMode = mode;
+        mHiSyncId = hiSyncId;
+    }
+
+    @DeviceSide
+    public int getSide() {
+        return mSide;
+    }
+
+    @DeviceMode
+    public int getMode() {
+        return mMode;
+    }
+
+    public long getHiSyncId() {
+        return mHiSyncId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof HearingAidInfo)) {
+            return false;
+        }
+        HearingAidInfo that = (HearingAidInfo) o;
+        return mSide == that.mSide && mMode == that.mMode && mHiSyncId == that.mHiSyncId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSide, mMode, mHiSyncId);
+    }
+
+    @Override
+    public String toString() {
+        return "HearingAidInfo{"
+                + "mSide=" + mSide
+                + ", mMode=" + mMode
+                + ", mHiSyncId=" + mHiSyncId
+                + '}';
+    }
+
+    @DeviceSide
+    private static int convertAshaDeviceSideToInternalSide(int ashaDeviceSide) {
+        return ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.get(
+                ashaDeviceSide, DeviceSide.SIDE_INVALID);
+    }
+
+    @DeviceMode
+    private static int convertAshaDeviceModeToInternalMode(int ashaDeviceMode) {
+        return ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.get(
+                ashaDeviceMode, DeviceMode.MODE_INVALID);
+    }
+
+    @DeviceSide
+    private static int convertLeAudioLocationToInternalSide(int leAudioLocation) {
+        boolean isLeft = (leAudioLocation & LE_AUDIO_LOCATION_LEFT) != 0;
+        boolean isRight = (leAudioLocation & LE_AUDIO_LOCATION_RIGHT) != 0;
+        if (isLeft && isRight) {
+            return DeviceSide.SIDE_LEFT_AND_RIGHT;
+        } else if (isLeft) {
+            return DeviceSide.SIDE_LEFT;
+        } else if (isRight) {
+            return DeviceSide.SIDE_RIGHT;
+        }
+        return DeviceSide.SIDE_INVALID;
+    }
+
+    @DeviceMode
+    private static int convertHapDeviceTypeToInternalMode(int hapDeviceType) {
+        return HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.get(hapDeviceType, DeviceMode.MODE_INVALID);
+    }
+
+    /** Builder class for constructing {@link HearingAidInfo} objects. */
+    public static final class Builder {
+        private int mSide = DeviceSide.SIDE_INVALID;
+        private int mMode = DeviceMode.MODE_INVALID;
+        private long mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+
+        /**
+         * Configure the hearing device mode.
+         * @param ashaDeviceMode one of the hearing aid device modes defined in HearingAidProfile
+         * {@link HearingAidProfile.DeviceMode}
+         */
+        public Builder setAshaDeviceMode(int ashaDeviceMode) {
+            mMode = convertAshaDeviceModeToInternalMode(ashaDeviceMode);
+            return this;
+        }
+
+        /**
+         * Configure the hearing device mode.
+         * @param hapDeviceType one of the hearing aid device types defined in HapClientProfile
+         * {@link HapClientProfile.HearingAidType}
+         */
+        public Builder setHapDeviceType(int hapDeviceType) {
+            mMode = convertHapDeviceTypeToInternalMode(hapDeviceType);
+            return this;
+        }
+
+        /**
+         * Configure the hearing device side.
+         * @param ashaDeviceSide one of the hearing aid device sides defined in HearingAidProfile
+         * {@link HearingAidProfile.DeviceSide}
+         */
+        public Builder setAshaDeviceSide(int ashaDeviceSide) {
+            mSide = convertAshaDeviceSideToInternalSide(ashaDeviceSide);
+            return this;
+        }
+
+        /**
+         * Configure the hearing device side.
+         * @param leAudioLocation one of the audio location defined in BluetoothLeAudio
+         * {@link BluetoothLeAudio.AudioLocation}
+         */
+        public Builder setLeAudioLocation(int leAudioLocation) {
+            mSide = convertLeAudioLocationToInternalSide(leAudioLocation);
+            return this;
+        }
+
+        /**
+         * Configure the hearing aid hiSyncId.
+         * @param hiSyncId the ASHA hearing aid id
+         */
+        public Builder setHiSyncId(long hiSyncId) {
+            mHiSyncId = hiSyncId;
+            return this;
+        }
+
+        /** Build the configured {@link HearingAidInfo} */
+        public HearingAidInfo build() {
+            return new HearingAidInfo(mSide, mMode, mHiSyncId);
+        }
+    }
+
+    private static final int LE_AUDIO_LOCATION_LEFT =
+            BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BACK_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_OF_CENTER
+                    | BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_WIDE
+                    | BluetoothLeAudio.AUDIO_LOCATION_LEFT_SURROUND;
+
+    private static final int LE_AUDIO_LOCATION_RIGHT =
+            BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BACK_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER
+                    | BluetoothLeAudio.AUDIO_LOCATION_SIDE_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_WIDE
+                    | BluetoothLeAudio.AUDIO_LOCATION_RIGHT_SURROUND;
+
+    private static final SparseIntArray ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING;
+    private static final SparseIntArray ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING;
+    private static final SparseIntArray HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING;
+
+    static {
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING = new SparseIntArray();
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+                HearingAidProfile.DeviceSide.SIDE_INVALID, DeviceSide.SIDE_INVALID);
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+                HearingAidProfile.DeviceSide.SIDE_LEFT, DeviceSide.SIDE_LEFT);
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+                HearingAidProfile.DeviceSide.SIDE_RIGHT, DeviceSide.SIDE_RIGHT);
+
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING = new SparseIntArray();
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+                HearingAidProfile.DeviceMode.MODE_INVALID, DeviceMode.MODE_INVALID);
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+                HearingAidProfile.DeviceMode.MODE_MONAURAL, DeviceMode.MODE_MONAURAL);
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+                HearingAidProfile.DeviceMode.MODE_BINAURAL, DeviceMode.MODE_BINAURAL);
+
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING = new SparseIntArray();
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_INVALID, DeviceMode.MODE_INVALID);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_BINAURAL, DeviceMode.MODE_BINAURAL);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_MONAURAL, DeviceMode.MODE_MONAURAL);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_BANDED, DeviceMode.MODE_BANDED);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_RFU, DeviceMode.MODE_INVALID);
+
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index fb861da..a3c2e70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHearingAid;
@@ -103,6 +104,7 @@
     private PbapClientProfile mPbapClientProfile;
     private PbapServerProfile mPbapProfile;
     private HearingAidProfile mHearingAidProfile;
+    private HapClientProfile mHapClientProfile;
     private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
     private LeAudioProfile mLeAudioProfile;
     private LocalBluetoothLeBroadcast mLeAudioBroadcast;
@@ -189,6 +191,12 @@
             addProfile(mHearingAidProfile, HearingAidProfile.NAME,
                     BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         }
+        if (mHapClientProfile == null && supportedList.contains(BluetoothProfile.HAP_CLIENT)) {
+            if (DEBUG) Log.d(TAG, "Adding local HAP_CLIENT profile");
+            mHapClientProfile = new HapClientProfile(mContext, mDeviceManager, this);
+            addProfile(mHapClientProfile, HapClientProfile.NAME,
+                    BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
+        }
         if (mHidProfile == null && supportedList.contains(BluetoothProfile.HID_HOST)) {
             if (DEBUG) Log.d(TAG, "Adding local HID_HOST profile");
             mHidProfile = new HidProfile(mContext, mDeviceManager, this);
@@ -337,25 +345,44 @@
                 Log.i(TAG, "Failed to connect " + mProfile + " device");
             }
 
-            if (getHearingAidProfile() != null &&
-                mProfile instanceof HearingAidProfile &&
-                (newState == BluetoothProfile.STATE_CONNECTED)) {
-                final int side = getHearingAidProfile().getDeviceSide(cachedDevice.getDevice());
-                final int mode = getHearingAidProfile().getDeviceMode(cachedDevice.getDevice());
-                cachedDevice.setDeviceSide(side);
-                cachedDevice.setDeviceMode(mode);
+            if (getHearingAidProfile() != null
+                    && mProfile instanceof HearingAidProfile
+                    && (newState == BluetoothProfile.STATE_CONNECTED)) {
 
                 // Check if the HiSyncID has being initialized
                 if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
                     long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
                     if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
-                        cachedDevice.setHiSyncId(newHiSyncId);
+                        final BluetoothDevice device = cachedDevice.getDevice();
+                        final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                                .setAshaDeviceSide(getHearingAidProfile().getDeviceSide(device))
+                                .setAshaDeviceMode(getHearingAidProfile().getDeviceMode(device))
+                                .setHiSyncId(newHiSyncId);
+                        cachedDevice.setHearingAidInfo(infoBuilder.build());
                     }
                 }
-
                 HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
             }
 
+            final boolean isHapClientProfile = getHapClientProfile() != null
+                    && mProfile instanceof HapClientProfile;
+            final boolean isLeAudioProfile = getLeAudioProfile() != null
+                    && mProfile instanceof LeAudioProfile;
+            final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile;
+            if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) {
+
+                // Checks if both profiles are connected to the device. Hearing aid info need
+                // to be retrieved from these profiles separately.
+                if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
+                    final BluetoothDevice device = cachedDevice.getDevice();
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setLeAudioLocation(getLeAudioProfile().getAudioLocation(device))
+                            .setHapDeviceType(getHapClientProfile().getHearingAidType(device));
+                    cachedDevice.setHearingAidInfo(infoBuilder.build());
+                    HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
+                }
+            }
+
             if (getCsipSetCoordinatorProfile() != null
                     && mProfile instanceof CsipSetCoordinatorProfile
                     && newState == BluetoothProfile.STATE_CONNECTED) {
@@ -524,6 +551,10 @@
         return mHearingAidProfile;
     }
 
+    public HapClientProfile getHapClientProfile() {
+        return mHapClientProfile;
+    }
+
     public LeAudioProfile getLeAudioProfile() {
         return mLeAudioProfile;
     }
@@ -675,6 +706,11 @@
             removedProfiles.remove(mHearingAidProfile);
         }
 
+        if (mHapClientProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.HAS)) {
+            profiles.add(mHapClientProfile);
+            removedProfiles.remove(mHapClientProfile);
+        }
+
         if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) {
             profiles.add(mSapProfile);
             removedProfiles.remove(mSapProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 132a631..8b68a09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -66,7 +66,7 @@
     public BatteryStatus(Intent batteryChangedIntent) {
         status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
         plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
-        level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+        level = getBatteryLevel(batteryChangedIntent);
         health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
 
@@ -188,7 +188,7 @@
      */
     public static boolean isCharged(Intent batteryChangedIntent) {
         int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
-        int level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+        int level = getBatteryLevel(batteryChangedIntent);
         return isCharged(status, level);
     }
 
@@ -204,4 +204,13 @@
     public static boolean isCharged(int status, int level) {
         return status == BATTERY_STATUS_FULL || level >= 100;
     }
+
+    /** Gets the battery level from the intent. */
+    public static int getBatteryLevel(Intent batteryChangedIntent) {
+        final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+        final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+        return scale == 0
+                ? -1 /*invalid battery level*/
+                : Math.round((level / (float) scale) * 100f);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
index df6929e..b3a52b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
@@ -16,6 +16,7 @@
 package com.android.settingslib.media;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
 import android.media.MediaRoute2Info;
 
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -32,7 +33,9 @@
      */
     public static String getId(CachedBluetoothDevice cachedDevice) {
         if (cachedDevice.isHearingAidDevice()) {
-            return Long.toString(cachedDevice.getHiSyncId());
+            if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                return Long.toString(cachedDevice.getHiSyncId());
+            }
         }
         return cachedDevice.getAddress();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 336cdd3..291f6a3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -317,7 +317,9 @@
 
     @Test
     public void getBatteryStatus_statusIsFull_returnFullString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+        final Intent intent = new Intent()
+                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -326,7 +328,9 @@
 
     @Test
     public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+        final Intent intent = new Intent()
+                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 39875f7..96e64ea 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -755,28 +755,30 @@
 
     @Test
     public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
+        UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
         ApplicationInfo appInfo = createApplicationInfo(PKG_1);
         AppEntry primaryUserApp = createAppEntry(appInfo, 1);
 
-        assertThat(primaryUserApp.shouldShowInPersonalTab(mContext, appInfo.uid)).isTrue();
+        assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
     }
 
     @Test
     public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
+        UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
                 Build.VERSION_CODES.TIRAMISU);
         // Create an app (and subsequent AppEntry) in a non-primary user profile.
         ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
         AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
 
-        assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(mContext, appInfo1.uid)).isFalse();
+        assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(um, appInfo1.uid)).isFalse();
     }
 
     @Test
     public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
         // Mark system user as parent for both profile users.
-        ShadowUserManager shadowUserManager = Shadow
-                .extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+        UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
+        ShadowUserManager shadowUserManager = Shadow.extract(um);
         shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
                 CLONE_USER, 0);
         shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
@@ -796,7 +798,7 @@
         AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
         AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
 
-        assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(mContext, appInfo1.uid)).isTrue();
-        assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(mContext, appInfo2.uid)).isFalse();
+        assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(um, appInfo1.uid)).isTrue();
+        assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(um, appInfo2.uid)).isFalse();
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 61802a8..f06623d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -327,8 +327,10 @@
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
-        cachedDevice1.setHiSyncId(HISYNCID1);
-        cachedDevice2.setHiSyncId(HISYNCID1);
+        cachedDevice1.setHearingAidInfo(
+                new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
+        cachedDevice2.setHearingAidInfo(
+                new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
         cachedDevice1.setSubDevice(cachedDevice2);
 
         // Call onDeviceUnpaired for the one in mCachedDevices.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 79e9938..65671a2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -29,6 +29,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
@@ -74,6 +75,10 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private HapClientProfile mHapClientProfile;
+    @Mock
+    private LeAudioProfile mLeAudioProfile;
+    @Mock
     private BluetoothDevice mDevice;
     @Mock
     private BluetoothDevice mSubDevice;
@@ -354,7 +359,7 @@
         assertThat(mCachedDevice.getConnectionSummary()).isNull();
 
         // Set device as Active for Hearing Aid and test connection state summary
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
 
@@ -399,7 +404,7 @@
         //   1. Profile:       {HEARING_AID, Connected, Active, Right ear}
         //   2. Audio Manager: In Call
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
 
@@ -413,9 +418,9 @@
         // Arrange:
         //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
         //   2. Audio Manager: In Call
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
-        mSubCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+        mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
         mCachedDevice.setSubDevice(mSubCachedDevice);
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
@@ -443,6 +448,26 @@
     }
 
     @Test
+    public void getConnectionSummary_testActiveDeviceLeAudioHearingAid() {
+        // Test without battery level
+        // Set HAP Client and LE Audio profile to be connected and test connection state summary
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
+        updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+        // Set device as Active for LE Audio and test connection state summary
+        mCachedDevice.setHearingAidInfo(getLeftLeAudioHearingAidInfo());
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.LE_AUDIO);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
+
+        // Set LE Audio profile to be disconnected and test connection state summary
+        mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.LE_AUDIO);
+        mCachedDevice.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+    }
+
+    @Test
     public void getConnectionSummary_testMultipleProfilesActiveDevice() {
         // Test without battery level
         // Set A2DP and HFP profiles to be connected and test connection state summary
@@ -859,21 +884,21 @@
     }
 
     @Test
-    public void isConnectedHearingAidDevice_connected_returnTrue() {
+    public void isConnectedAshaHearingAidDevice_connected_returnTrue() {
         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
         when(mHearingAidProfile.getConnectionStatus(mDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
 
-        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
+        assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isTrue();
     }
 
     @Test
-    public void isConnectedHearingAidDevice_disconnected_returnFalse() {
+    public void isConnectedAshaHearingAidDevice_disconnected_returnFalse() {
         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
         when(mHearingAidProfile.getConnectionStatus(mDevice)).
                 thenReturn(BluetoothProfile.STATE_DISCONNECTED);
 
-        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+        assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isFalse();
     }
 
     @Test
@@ -891,10 +916,10 @@
     }
 
     @Test
-    public void isConnectedHearingAidDevice_profileIsNull_returnFalse() {
+    public void isConnectedAshaHearingAidDevice_profileIsNull_returnFalse() {
         when(mProfileManager.getHearingAidProfile()).thenReturn(null);
 
-        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+        assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isFalse();
     }
 
     @Test
@@ -965,10 +990,10 @@
 
         mCachedDevice.mRssi = RSSI_1;
         mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mSubCachedDevice.mRssi = RSSI_2;
         mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
-        mSubCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        mSubCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         mCachedDevice.setSubDevice(mSubCachedDevice);
 
         mCachedDevice.switchSubDeviceContent();
@@ -976,22 +1001,20 @@
         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
-        assertThat(mCachedDevice.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
-        assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_LEFT);
+        assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
     }
 
     @Test
     public void getConnectionSummary_profileConnectedFail_showErrorMessage() {
-        final A2dpProfile profle = mock(A2dpProfile.class);
-        mCachedDevice.onProfileStateChanged(profle, BluetoothProfile.STATE_CONNECTED);
+        final A2dpProfile profile = mock(A2dpProfile.class);
+        mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
         mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
 
-        when(profle.getConnectionStatus(mDevice)).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        when(profile.getConnectionStatus(mDevice)).thenReturn(BluetoothProfile.STATE_CONNECTED);
 
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
                 mContext.getString(R.string.profile_connect_timeout_subtext));
@@ -1069,4 +1092,55 @@
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
         assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
     }
+
+    @Test
+    public void isConnectedHearingAidDevice_isConnectedAshaHearingAidDevice_returnTrue() {
+        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
+    }
+
+    @Test
+    public void isConnectedHearingAidDevice_isConnectedLeAudioHearingAidDevice_returnTrue() {
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+        updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
+        updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
+    }
+
+    @Test
+    public void isConnectedHearingAidDevice_isNotAnyConnectedHearingAidDevice_returnFalse() {
+        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_DISCONNECTED);
+        updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
+
+        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+    }
+
+    private HearingAidInfo getLeftAshaHearingAidInfo() {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
+                .build();
+    }
+
+    private HearingAidInfo getRightAshaHearingAidInfo() {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT)
+                .build();
+    }
+
+    private HearingAidInfo getLeftLeAudioHearingAidInfo() {
+        return new HearingAidInfo.Builder()
+                .setLeAudioLocation(BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT)
+                .build();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 611b0a4..470d8e0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -106,7 +107,7 @@
      * deviceSide, deviceMode.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfos() {
+    public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
                 HearingAidProfile.DeviceMode.MODE_BINAURAL);
@@ -118,21 +119,22 @@
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
         assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+                HearingAidInfo.DeviceSide.SIDE_RIGHT);
     }
 
     /**
      * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHiSyncId() {
+    public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
                 BluetoothHearingAid.HI_SYNC_ID_INVALID);
+
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
 
-        verify(mCachedDevice1, never()).setHiSyncId(anyLong());
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
     }
 
     /**
@@ -140,9 +142,10 @@
      */
     @Test
     public void setSubDeviceIfNeeded_sameHiSyncId_setSubDevice() {
-        mCachedDevice1.setHiSyncId(HISYNCID1);
-        mCachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+        mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
         mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
 
         assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
@@ -153,9 +156,10 @@
      */
     @Test
     public void setSubDeviceIfNeeded_differentHiSyncId_notSetSubDevice() {
-        mCachedDevice1.setHiSyncId(HISYNCID1);
-        mCachedDevice2.setHiSyncId(HISYNCID2);
+        mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+        mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID2));
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
         mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
 
         assertThat(mCachedDevice1.getSubDevice()).isNull();
@@ -276,9 +280,9 @@
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
         assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+                HearingAidInfo.DeviceSide.SIDE_RIGHT);
         verify(mHearingAidDeviceManager).onHiSyncIdChanged(HISYNCID1);
     }
 
@@ -389,11 +393,9 @@
     @Test
     public void onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceConnected_switch()
     {
-        when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
-        mCachedDevice1.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
-        when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
+        mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+        mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
         when(mCachedDevice2.isConnected()).thenReturn(true);
-        mCachedDevice2.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
         mCachedDevice1.setSubDevice(mCachedDevice2);
 
@@ -404,10 +406,8 @@
 
         assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2);
         assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1);
-        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
-        assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_LEFT);
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+        assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
         verify(mCachedDevice1).refresh();
     }
 
@@ -455,4 +455,18 @@
         assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)).
                 isEqualTo(mCachedDevice1);
     }
+
+    private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+                .setHiSyncId(hiSyncId)
+                .build();
+    }
+
+    private HearingAidInfo getRightAshaHearingAidInfo(long hiSyncId) {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
+                .setHiSyncId(hiSyncId)
+                .build();
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 01c0809..680a0a1 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -771,6 +771,80 @@
     <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
     <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
 
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
+    <uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
+    <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
+    <uses-permission android:name="android.permission.health.READ_BODY_FAT" />
+    <uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.READ_BODY_WATER_MASS" />
+    <uses-permission android:name="android.permission.health.READ_BONE_MASS" />
+    <uses-permission android:name="android.permission.health.READ_CERVICAL_MUCUS" />
+    <uses-permission android:name="android.permission.health.READ_DISTANCE" />
+    <uses-permission android:name="android.permission.health.READ_ELEVATION_GAINED" />
+    <uses-permission android:name="android.permission.health.READ_EXERCISE" />
+    <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
+    <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
+    <uses-permission android:name="android.permission.health.READ_HEIGHT" />
+    <uses-permission android:name="android.permission.health.READ_HIP_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.READ_HYDRATION" />
+    <uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS" />
+    <uses-permission android:name="android.permission.health.READ_MENSTRUATION" />
+    <uses-permission android:name="android.permission.health.READ_NUTRITION" />
+    <uses-permission android:name="android.permission.health.READ_OVULATION_TEST" />
+    <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION" />
+    <uses-permission android:name="android.permission.health.READ_POWER" />
+    <uses-permission android:name="android.permission.health.READ_RESPIRATORY_RATE" />
+    <uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.READ_SEXUAL_ACTIVITY" />
+    <uses-permission android:name="android.permission.health.READ_SLEEP" />
+    <uses-permission android:name="android.permission.health.READ_SPEED" />
+    <uses-permission android:name="android.permission.health.READ_STEPS" />
+    <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.READ_VO2_MAX" />
+    <uses-permission android:name="android.permission.health.READ_WAIST_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.READ_WEIGHT" />
+    <uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES" />
+    <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.WRITE_BASAL_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
+    <uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE" />
+    <uses-permission android:name="android.permission.health.WRITE_BODY_FAT" />
+    <uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.WRITE_BODY_WATER_MASS" />
+    <uses-permission android:name="android.permission.health.WRITE_BONE_MASS" />
+    <uses-permission android:name="android.permission.health.WRITE_CERVICAL_MUCUS" />
+    <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
+    <uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
+    <uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
+    <uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED" />
+    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
+    <uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
+    <uses-permission android:name="android.permission.health.WRITE_HIP_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.WRITE_HYDRATION" />
+    <uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS" />
+    <uses-permission android:name="android.permission.health.WRITE_MENSTRUATION" />
+    <uses-permission android:name="android.permission.health.WRITE_NUTRITION" />
+    <uses-permission android:name="android.permission.health.WRITE_OVULATION_TEST" />
+    <uses-permission android:name="android.permission.health.WRITE_OXYGEN_SATURATION" />
+    <uses-permission android:name="android.permission.health.WRITE_POWER" />
+    <uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_SEXUAL_ACTIVITY" />
+    <uses-permission android:name="android.permission.health.WRITE_SLEEP" />
+    <uses-permission android:name="android.permission.health.WRITE_SPEED" />
+    <uses-permission android:name="android.permission.health.WRITE_STEPS" />
+    <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.WRITE_VO2_MAX" />
+    <uses-permission android:name="android.permission.health.WRITE_WAIST_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.WRITE_WEIGHT" />
+    <uses-permission android:name="android.permission.health.WRITE_WHEELCHAIR_PUSHES" />
+
     <!-- Permission required for CTS test - ApplicationExemptionsTests -->
     <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5c70edaf..87354c7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -170,7 +170,6 @@
 android_library {
     name: "SystemUI-tests-base",
     manifest: "tests/AndroidManifest-base.xml",
-    
     resource_dirs: [
         "tests/res",
         "res-product",
@@ -224,30 +223,21 @@
         "LowLightDreamLib",
         "motion_tool_lib",
     ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-        "android.test.mock",
-    ],
 }
 
-// Device tests only
 android_library {
     name: "SystemUI-tests",
-    manifest: "tests/AndroidManifest.xml",
+    manifest: "tests/AndroidManifest-base.xml",
     additional_manifests: ["tests/AndroidManifest.xml"],
-    resource_dirs: [],
     srcs: [
-        // Kotlin likes all files in the same module for internal
+        "tests/src/**/*.kt",
+        "tests/src/**/*.java",
         "src/**/*.kt",
         "src/**/*.java",
         "src/**/I*.aidl",
         ":ReleaseJavaFiles",
-        "tests/src/**/*.kt",
-        "tests/src/**/*.java",
         ":SystemUI-tests-utils",
     ],
-    dont_merge_manifests: true,
     static_libs: [
         "SystemUI-tests-base",
         "androidx.test.uiautomator_uiautomator",
@@ -276,12 +266,6 @@
         "platform_app_defaults",
         "SystemUI_app_defaults",
     ],
-    srcs: [
-        "src/**/*.kt",
-        "src/**/*.java",
-        "src/**/I*.aidl",
-        ":ReleaseJavaFiles",
-    ],
     manifest: "tests/AndroidManifest-base.xml",
     static_libs: [
         "SystemUI-tests-base",
@@ -296,13 +280,6 @@
     certificate: "platform",
     privileged: true,
     resource_dirs: [],
-
-    kotlincflags: ["-Xjvm-default=enable"],
-    dxflags: ["--multi-dex"],
-    required: [
-        "privapp_whitelist_com.android.systemui",
-    ],
-    plugins: ["dagger2-compiler"],
 }
 
 android_robolectric_test {
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index f7bcf1f..5df79e1 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -36,8 +36,29 @@
 
     static_libs: [
         "PluginCoreLib",
+        "androidx.core_core-animation-nodeps",
     ],
 
     manifest: "AndroidManifest.xml",
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+android_test {
+    name: "SystemUIAnimationLibTests",
+
+    static_libs: [
+        "SystemUIAnimationLib",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "testables",
+    ],
+    libs: [
+        "android.test.base",
+    ],
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    test_suites: ["general-tests"],
+}
diff --git a/packages/SystemUI/animation/TEST_MAPPING b/packages/SystemUI/animation/TEST_MAPPING
new file mode 100644
index 0000000..3dc8510
--- /dev/null
+++ b/packages/SystemUI/animation/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "SystemUIAnimationLibTests"
+    }
+  ]
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 8063483..9dbb920 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -27,7 +27,10 @@
 import android.view.animation.PathInterpolator;
 
 /**
- * Utility class to receive interpolators from
+ * Utility class to receive interpolators from.
+ *
+ * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
+ * Please consider using the androidx dependencies featuring better testability altogether.
  */
 public class Interpolators {
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
new file mode 100644
index 0000000..8da87feb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
@@ -0,0 +1,219 @@
+/*
+ * 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.animation;
+
+import android.graphics.Path;
+import android.util.MathUtils;
+
+import androidx.core.animation.AccelerateDecelerateInterpolator;
+import androidx.core.animation.AccelerateInterpolator;
+import androidx.core.animation.BounceInterpolator;
+import androidx.core.animation.DecelerateInterpolator;
+import androidx.core.animation.Interpolator;
+import androidx.core.animation.LinearInterpolator;
+import androidx.core.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from. (androidx compatible version)
+ *
+ * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
+ * this class are also reflected in {@link Interpolators}.
+ *
+ * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
+ * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
+ * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
+ * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
+ * advancing the time).
+ */
+public class InterpolatorsAndroidX {
+
+    /*
+     * ============================================================================================
+     * Emphasized interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
+     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+     * is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 0.8f, 0.15f);
+
+    /**
+     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+     * is appearing e.g. when coming from off screen
+     */
+    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+            0.05f, 0.7f, 0.1f, 1f);
+
+
+    /*
+     * ============================================================================================
+     * Standard interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The standard interpolator that should be used on every normal animation
+     */
+    public static final Interpolator STANDARD = new PathInterpolator(
+            0.2f, 0f, 0f, 1f);
+
+    /**
+     * The standard accelerating interpolator that should be used on every regular movement of
+     * content that is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 1f, 1f);
+
+    /**
+     * The standard decelerating interpolator that should be used on every regular movement of
+     * content that is appearing e.g. when coming from off screen.
+     */
+    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+            0f, 0f, 0f, 1f);
+
+    /*
+     * ============================================================================================
+     * Legacy
+     * ============================================================================================
+     */
+
+    /**
+     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    /**
+     * The default legacy accelerating interpolator as defined in Material 1.
+     * Also known as FAST_OUT_LINEAR_IN.
+     */
+    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+    /**
+     * The default legacy decelerating interpolator as defined in Material 1.
+     * Also known as LINEAR_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+    /**
+     * Linear interpolator. Often used if the interpolator is for different properties who need
+     * different interpolations.
+     */
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    /*
+    * ============================================================================================
+    * Custom interpolators
+    * ============================================================================================
+    */
+
+    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
+
+    /**
+     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
+            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
+    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
+    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
+    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.1f);
+    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
+            1);
+    public static final Interpolator BOUNCE = new BounceInterpolator();
+    /**
+     * For state transitions on the control panel that lives in GlobalActions.
+     */
+    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.0f);
+
+    /**
+     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+     */
+    public static final Interpolator TOUCH_RESPONSE =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+    /**
+     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator TOUCH_RESPONSE_REVERSE =
+            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+    /*
+     * ============================================================================================
+     * Functions / Utilities
+     * ============================================================================================
+     */
+
+    /**
+     * Calculate the amount of overshoot using an exponential falloff function with desired
+     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
+     * overshoot, retaining its acceleration.
+     *
+     * @param progress a progress value going from 0 to 1
+     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
+     *                        value of the overall progress will be at 1.1.
+     * @param overshootStart the point in (0,1] where the result should reach 1
+     * @return the interpolated overshoot
+     */
+    public static float getOvershootInterpolation(float progress, float overshootAmount,
+            float overshootStart) {
+        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
+            throw new IllegalArgumentException("Invalid values for overshoot");
+        }
+        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
+        return MathUtils.max(0.0f,
+                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
+    }
+
+    /**
+     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
+     * starts immediately here, instead of first having a section of non-overshooting
+     *
+     * @param progress a progress value going from 0 to 1
+     */
+    public static float getOvershootInterpolation(float progress) {
+        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
+    }
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..43bfa74 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -320,9 +320,7 @@
                                 counterWallpaper.cleanUp(finishTransaction)
                                 // Release surface references now. This is apparently to free GPU
                                 // memory while doing quick operations (eg. during CTS).
-                                for (i in info.changes.indices.reversed()) {
-                                    info.changes[i].leash.release()
-                                }
+                                info.releaseAllSurfaces()
                                 for (i in leashMap.size - 1 downTo 0) {
                                     leashMap.valueAt(i).release()
                                 }
@@ -331,6 +329,7 @@
                                         null /* wct */,
                                         finishTransaction
                                     )
+                                    finishTransaction.close()
                                 } catch (e: RemoteException) {
                                     Log.e(
                                         "ActivityOptionsCompat",
@@ -364,6 +363,9 @@
                 ) {
                     // TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
                     //       ignore any incoming merges.
+                    // Clean up stuff though cuz GC takes too long for benchmark tests.
+                    t.close()
+                    info.releaseAllSurfaces()
                 }
             }
         }
diff --git a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
new file mode 100644
index 0000000..389eed0
--- /dev/null
+++ b/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -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 com.android.systemui.animation
+
+import androidx.test.filters.SmallTest
+import java.lang.reflect.Modifier
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class InterpolatorsAndroidXTest {
+
+    @Test
+    fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
+        assertEquals(
+            Interpolators::class.java.getPublicMethods(),
+            InterpolatorsAndroidX::class.java.getPublicMethods()
+        )
+    }
+
+    @Test
+    fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
+        assertEquals(
+            Interpolators::class.java.getPublicFields(),
+            InterpolatorsAndroidX::class.java.getPublicFields()
+        )
+    }
+
+    private fun <T> Class<T>.getPublicMethods() =
+        declaredMethods
+            .filter { Modifier.isPublic(it.modifiers) }
+            .map { it.toString().replace(name, "") }
+            .toSet()
+
+    private fun <T> Class<T>.getPublicFields() =
+        fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
+}
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
index e611e8b..979e1a0 100644
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -38,12 +38,18 @@
 import platform.test.screenshot.getEmulatedDevicePathConfig
 
 /** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ComposeScreenshotTestRule(
+    emulationSpec: DeviceEmulationSpec,
+    assetPathRelativeToBuildRoot: String
+) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            SystemUIGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetPathRelativeToBuildRoot
+            )
         )
     private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
     private val delegateRule =
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
new file mode 100644
index 0000000..e850d68
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="#1f1f1f"
+      android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
new file mode 100644
index 0000000..91b9ae5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="#1f1f1f"
+      android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 2b7bdc2..c772c96 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,7 +27,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+    androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size"
     android:layout_gravity="center_horizontal|bottom"
     android:clipChildren="false"
     android:clipToPadding="false">
diff --git a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml b/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
deleted file mode 100644
index a3c37e4..0000000
--- a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/dimens.xml
-**
-** Copyright 2013, 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>
-    <!-- Height of the sliding KeyguardSecurityContainer
-        (includes 2x keyguard_security_view_top_margin) -->
-    <dimen name="keyguard_security_height">550dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
index 1dc61c5..b7a1bb4 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
@@ -17,10 +17,5 @@
 */
 -->
 <resources>
-
-    <!-- Height of the sliding KeyguardSecurityContainer
-         (includes 2x keyguard_security_view_top_margin) -->
-    <dimen name="keyguard_security_height">470dp</dimen>
-
     <dimen name="widget_big_font_size">100dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 3861d98..e6593b1 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -29,9 +29,6 @@
          (includes 2x keyguard_security_view_top_margin) -->
     <dimen name="keyguard_security_height">420dp</dimen>
 
-    <!-- Max Height of the sliding KeyguardSecurityContainer
-         (includes 2x keyguard_security_view_top_margin) -->
-
     <!-- pin/password field max height -->
     <dimen name="keyguard_password_height">80dp</dimen>
 
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 6e0e38b..88f138f 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -71,8 +71,8 @@
         <com.android.internal.widget.LockPatternView
             android:id="@+id/lockPattern"
             android:layout_gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
+            android:layout_width="@dimen/biometric_auth_pattern_view_size"
+            android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
 
         <TextView
             android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 891c6af..81ca3718 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -67,8 +67,8 @@
         <com.android.internal.widget.LockPatternView
             android:id="@+id/lockPattern"
             android:layout_gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
+            android:layout_width="@dimen/biometric_auth_pattern_view_size"
+            android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
 
         <TextView
             android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index f7600e6..64aa629 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -55,6 +55,7 @@
             android:id="@+id/status_bar_start_side_container"
             android:layout_height="match_parent"
             android:layout_width="0dp"
+            android:clipChildren="false"
             android:layout_weight="1">
 
             <!-- Container that is wrapped around the views on the start half of the status bar.
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 49ef330..fff2544 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,6 +40,10 @@
     <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">116dp</dimen>
 
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
     <dimen name="global_actions_power_dialog_item_height">130dp</dimen>
     <dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen>
 
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index aefd998..a0e721e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -29,11 +29,11 @@
 
     <style name="AuthCredentialPatternContainerStyle">
         <item name="android:gravity">center</item>
-        <item name="android:maxHeight">320dp</item>
-        <item name="android:maxWidth">320dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">60dp</item>
+        <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:paddingHorizontal">32dp</item>
         <item name="android:paddingVertical">20dp</item>
     </style>
 
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 16152f8..08e1bf2 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -78,9 +78,6 @@
     <color name="biometric_dialog_accent">@color/material_dynamic_primary70</color>
     <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
 
-    <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#7DA7F1</color>
-
     <color name="GM2_green_500">#FF41Af6A</color>
     <color name="GM2_blue_500">#5195EA</color>
     <color name="GM2_red_500">#E25142</color>
@@ -103,4 +100,13 @@
     <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
 
     <color name="people_tile_background">@color/material_dynamic_secondary20</color>
+
+    <!-- UDFPS colors -->
+    <color name="udfps_enroll_icon">#7DA7F1</color>
+    <color name="udfps_moving_target_fill">#475670</color>
+    <!-- 50% of udfps_moving_target_fill-->
+    <color name="udfps_moving_target_fill_error">#80475670</color>
+    <color name="udfps_enroll_progress">#7DA7F1</color>
+    <color name="udfps_enroll_progress_help">#607DA7F1</color>
+    <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 </resources>
diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml
index 65ca70b..03365b3 100644
--- a/packages/SystemUI/res/values-sw360dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw360dp/dimens.xml
@@ -25,5 +25,8 @@
 
     <!-- Home Controls -->
     <dimen name="global_actions_side_margin">12dp</dimen>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
new file mode 100644
index 0000000..1e26a69
--- /dev/null
+++ b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 78279ca..96af3c1 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -24,5 +24,8 @@
 
     <!-- Home Controls -->
     <dimen name="global_actions_side_margin">16dp</dimen>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
new file mode 100644
index 0000000..c4d9b9b
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index 7da47e5..ff6e005 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -27,4 +27,6 @@
     <dimen name="global_actions_grid_item_side_margin">12dp</dimen>
     <dimen name="global_actions_grid_item_height">72dp</dimen>
 
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 8148d3d..5ca2b43 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -16,16 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">120dp</item>
-        <item name="android:paddingVertical">40dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 771de08..41d931d 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -24,16 +24,6 @@
         <item name="android:layout_gravity">top</item>
     </style>
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">180dp</item>
-        <item name="android:paddingVertical">80dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 599bf30..9bc0dde 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -92,4 +92,6 @@
     <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
     <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
 
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index f9ed67d..d9406d3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -16,16 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">120dp</item>
-        <item name="android:paddingVertical">40dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 78d299c..41d931d 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -24,16 +24,6 @@
         <item name="android:layout_gravity">top</item>
     </style>
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">240dp</item>
-        <item name="android:paddingVertical">120dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 0705017..927059a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -22,5 +22,8 @@
     <dimen name="controls_padding_horizontal">75dp</dimen>
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..0d82217
--- /dev/null
+++ b/packages/SystemUI/res/values-sw800dp/dimens.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.
+  -->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 44ba3f6..5b6c9d3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -209,5 +209,15 @@
         <attr name="permissionGrantButtonTopStyle" format="reference"/>
         <attr name="permissionGrantButtonBottomStyle" format="reference"/>
     </declare-styleable>
+
+    <declare-styleable name="BiometricsEnrollView">
+        <attr name="biometricsEnrollStyle" format="reference" />
+        <attr name="biometricsEnrollIcon" format="reference|color" />
+        <attr name="biometricsMovingTargetFill" format="reference|color" />
+        <attr name="biometricsMovingTargetFillError" format="reference|color" />
+        <attr name="biometricsEnrollProgress" format="reference|color" />
+        <attr name="biometricsEnrollProgressHelp" format="reference|color" />
+        <attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 84cab6c..8ee39dd 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -134,12 +134,12 @@
     <color name="biometric_dialog_error">#ffd93025</color>                  <!-- red 600 -->
 
     <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#7DA7F1</color>
-    <color name="udfps_moving_target_fill">#475670</color>
+    <color name="udfps_enroll_icon">#699FF3</color>
+    <color name="udfps_moving_target_fill">#C2D7F7</color>
     <!-- 50% of udfps_moving_target_fill-->
-    <color name="udfps_moving_target_fill_error">#80475670</color>
-    <color name="udfps_enroll_progress">#7DA7F1</color>
-    <color name="udfps_enroll_progress_help">#607DA7F1</color>
+    <color name="udfps_moving_target_fill_error">#80C2D7F7</color>
+    <color name="udfps_enroll_progress">#699FF3</color>
+    <color name="udfps_enroll_progress_help">#70699FF3</color>
     <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 
     <!-- Floating overlay actions -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9f29c5b..569b661 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -959,6 +959,10 @@
     <!-- Biometric Auth Credential values -->
     <dimen name="biometric_auth_icon_size">48dp</dimen>
 
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
     <!-- Starting text size in sp of batteryLevel for wireless charging animation -->
     <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
         0
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fe4f639..aafa47f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -251,11 +251,12 @@
 
     <style name="AuthCredentialPatternContainerStyle">
         <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:padding">20dp</item>
+        <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:paddingHorizontal">32dp</item>
+        <item name="android:paddingVertical">20dp</item>
     </style>
 
     <style name="AuthCredentialPinPasswordContainerStyle">
@@ -314,6 +315,10 @@
 
         <!-- Needed for MediaRoute chooser dialog -->
         <item name="*android:isLightTheme">false</item>
+
+        <!-- Biometrics enroll color style -->
+        <item name="biometricsEnrollStyle">@style/BiometricsEnrollStyle</item>
+
     </style>
 
     <style name="Theme.SystemUI.LightWallpaper">
@@ -1281,7 +1286,6 @@
         <item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
     </style>
 
-
     <!-- The style for log access consent dialog -->
     <style name="LogAccessDialogTheme" parent="@style/Theme.SystemUI.Dialog.Alert">
         <item name="permissionGrantButtonTopStyle">@style/PermissionGrantButtonTop</item>
@@ -1321,4 +1325,13 @@
         <item name="android:layout_marginBottom">2dp</item>
         <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
     </style>
+
+    <style name="BiometricsEnrollStyle">
+        <item name="biometricsEnrollIcon">@color/udfps_enroll_icon</item>
+        <item name="biometricsMovingTargetFill">@color/udfps_moving_target_fill</item>
+        <item name="biometricsMovingTargetFillError">@color/udfps_moving_target_fill_error</item>
+        <item name="biometricsEnrollProgress">@color/udfps_enroll_progress</item>
+        <item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
+        <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 49cc483..e032bb9 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -34,13 +34,19 @@
 /**
  * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
  */
-class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ExternalViewScreenshotTestRule(
+    emulationSpec: DeviceEmulationSpec,
+    assetPathRelativeToBuildRoot: String
+) : TestRule {
 
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            SystemUIGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetPathRelativeToBuildRoot
+            )
         )
     private val delegateRule =
         RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
index fafc774..72d8c5a 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
@@ -23,11 +23,11 @@
 /** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
 class SystemUIGoldenImagePathManager(
     pathConfig: PathConfig,
-    override val assetsPathRelativeToRepo: String = "tests/screenshot/assets"
+    assetsPathRelativeToBuildRoot: String
 ) :
     GoldenImagePathManager(
         appContext = InstrumentationRegistry.getInstrumentation().context,
-        assetsPathRelativeToRepo = assetsPathRelativeToRepo,
+        assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
         deviceLocalPath =
             InstrumentationRegistry.getInstrumentation()
                 .targetContext
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 36ac1ff..6d0cc5e 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -44,17 +44,16 @@
     emulationSpec: DeviceEmulationSpec,
     private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
     pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
-    assetsPathRelativeToRepo: String = ""
+    assetsPathRelativeToBuildRoot: String
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            if (assetsPathRelativeToRepo.isBlank()) {
-                SystemUIGoldenImagePathManager(pathConfig)
-            } else {
-                SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
-            }
+            SystemUIGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetsPathRelativeToBuildRoot
+            )
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
     private val delegateRule =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 93c8073..1b0dacc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -166,15 +166,14 @@
                     counterLauncher.cleanUp(finishTransaction);
                     counterWallpaper.cleanUp(finishTransaction);
                     // Release surface references now. This is apparently to free GPU memory
-                    // while doing quick operations (eg. during CTS).
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        info.getChanges().get(i).getLeash().release();
-                    }
+                    // before GC would.
+                    info.releaseAllSurfaces();
                     // Don't release here since launcher might still be using them. Instead
                     // let launcher release them (eg. via RemoteAnimationTargets)
                     leashMap.clear();
                     try {
                         finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+                        finishTransaction.close();
                     } catch (RemoteException e) {
                         Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
                                 + " finished callback", e);
@@ -203,10 +202,13 @@
                 synchronized (mFinishRunnables) {
                     finishRunnable = mFinishRunnables.remove(mergeTarget);
                 }
+                // Since we're not actually animating, release native memory now
+                t.close();
+                info.releaseAllSurfaces();
                 if (finishRunnable == null) return;
                 onAnimationCancelled(false /* isKeyguardOccluded */);
                 finishRunnable.run();
             }
         };
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d4d3d25..b7e2494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -126,15 +126,18 @@
             public void mergeAnimation(IBinder transition, TransitionInfo info,
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishedCallback) {
-                if (!mergeTarget.equals(mToken)) return;
-                if (!mRecentsSession.merge(info, t, recents)) return;
-                try {
-                    finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error merging transition.", e);
+                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+                    try {
+                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error merging transition.", e);
+                    }
+                    // commit taskAppeared after merge transition finished.
+                    mRecentsSession.commitTasksAppearedIfNeeded(recents);
+                } else {
+                    t.close();
+                    info.releaseAllSurfaces();
                 }
-                // commit taskAppeared after merge transition finished.
-                mRecentsSession.commitTasksAppearedIfNeeded(recents);
             }
         };
         return new RemoteTransition(remote, appThread);
@@ -248,6 +251,8 @@
                 }
                 // In this case, we are "returning" to an already running app, so just consume
                 // the merge and do nothing.
+                info.releaseAllSurfaces();
+                t.close();
                 return true;
             }
             final int layer = mInfo.getChanges().size() * 3;
@@ -264,6 +269,8 @@
                 t.setLayer(targets[i].leash, layer);
             }
             t.apply();
+            // not using the incoming anim-only surfaces
+            info.releaseAnimSurfaces();
             mAppearedTargets = targets;
             return true;
         }
@@ -380,9 +387,7 @@
             }
             // Only release the non-local created surface references. The animator is responsible
             // for releasing the leashes created by local.
-            for (int i = 0; i < mInfo.getChanges().size(); ++i) {
-                mInfo.getChanges().get(i).getLeash().release();
-            }
+            mInfo.releaseAllSurfaces();
             // Reset all members.
             mWrapped = null;
             mFinishCB = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
             mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
         }
         mActivityTaskManager.stopSystemLockTaskMode();
-        mShadeController.collapsePanel(false);
+        mShadeController.collapseShade(false);
         if (mTelecomManager != null && mTelecomManager.isInCall()) {
             mTelecomManager.showInCallScreen(false);
             if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 8197685..e6283b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -26,7 +26,6 @@
     val credentialAttempted: Boolean,
     val deviceInteractive: Boolean,
     val dreaming: Boolean,
-    val encryptedOrLockdown: Boolean,
     val fingerprintDisabled: Boolean,
     val fingerprintLockedOut: Boolean,
     val goingToSleep: Boolean,
@@ -37,6 +36,7 @@
     val primaryUser: Boolean,
     val shouldListenSfpsState: Boolean,
     val shouldListenForFingerprintAssistant: Boolean,
+    val strongerAuthRequired: Boolean,
     val switchingUser: Boolean,
     val udfps: Boolean,
     val userDoesNotHaveTrust: Boolean
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 01be33e..4d0a273 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -363,16 +363,18 @@
         final boolean sfpsEnabled = getResources().getBoolean(
                 R.bool.config_show_sidefps_hint_on_bouncer);
         final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
-        final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+        final boolean isUnlockingWithFpAllowed =
+                mUpdateMonitor.isUnlockingWithFingerprintAllowed();
 
-        boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+        boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning
+                && isUnlockingWithFpAllowed;
 
         if (DEBUG) {
             Log.d(TAG, "sideFpsToShow=" + toShow + ", "
                     + "mBouncerVisible=" + mBouncerVisible + ", "
                     + "configEnabled=" + sfpsEnabled + ", "
                     + "fpsDetectionRunning=" + fpsDetectionRunning + ", "
-                    + "needsStrongAuth=" + needsStrongAuth);
+                    + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
         }
         if (toShow) {
             mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39ade34..993d80f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -27,6 +27,8 @@
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
 import static android.hardware.biometrics.BiometricConstants.LockoutMode;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -228,7 +230,15 @@
      * Biometric authentication: Cancelling and waiting for the relevant biometric service to
      * send us the confirmation that cancellation has happened.
      */
-    private static final int BIOMETRIC_STATE_CANCELLING = 2;
+    @VisibleForTesting
+    protected static final int BIOMETRIC_STATE_CANCELLING = 2;
+
+    /**
+     * Biometric state: During cancelling we got another request to start listening, so when we
+     * receive the cancellation done signal, we should start listening again.
+     */
+    @VisibleForTesting
+    protected static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
 
     /**
      * Action indicating keyguard *can* start biometric authentiation.
@@ -243,12 +253,6 @@
      */
     private static final int BIOMETRIC_ACTION_UPDATE = 2;
 
-    /**
-     * Biometric state: During cancelling we got another request to start listening, so when we
-     * receive the cancellation done signal, we should start listening again.
-     */
-    private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
-
     @VisibleForTesting
     public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
     public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
@@ -356,7 +360,8 @@
 
     private KeyguardBypassController mKeyguardBypassController;
     private List<SubscriptionInfo> mSubscriptionInfo;
-    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+    @VisibleForTesting
+    protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
@@ -790,7 +795,7 @@
                 new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
-            mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
+            mTrustManager.unlockedByBiometricForUser(userId, FINGERPRINT);
         }
         // Don't send cancel if authentication succeeds
         mFingerprintCancelSignal = null;
@@ -800,7 +805,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+                cb.onBiometricAuthenticated(userId, FINGERPRINT,
                         isStrongBiometric);
             }
         }
@@ -833,7 +838,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+                cb.onBiometricAuthFailed(FINGERPRINT);
             }
         }
         if (isUdfpsSupported()) {
@@ -858,7 +863,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo);
+                cb.onBiometricAcquired(FINGERPRINT, acquireInfo);
             }
         }
     }
@@ -892,7 +897,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
+                cb.onBiometricHelp(msgId, helpString, FINGERPRINT);
             }
         }
     }
@@ -944,7 +949,7 @@
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged = !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
-            mLogger.d("Fingerprint locked out - requiring strong auth");
+            mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
             mLockPatternUtils.requireStrongAuth(
                     STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
         }
@@ -953,6 +958,7 @@
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged |= !mFingerprintLockedOut;
             mFingerprintLockedOut = true;
+            mLogger.d("Fingerprint temporarily locked out - requiring stronger auth");
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             }
@@ -963,12 +969,12 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
+                cb.onBiometricError(msgId, errString, FINGERPRINT);
             }
         }
 
         if (lockedOutStateChanged) {
-            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+            notifyLockedOutStateChanged(FINGERPRINT);
         }
     }
 
@@ -996,7 +1002,7 @@
         }
 
         if (changed) {
-            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+            notifyLockedOutStateChanged(FINGERPRINT);
         }
     }
 
@@ -1019,7 +1025,7 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
-                        BiometricSourceType.FINGERPRINT);
+                        FINGERPRINT);
             }
         }
     }
@@ -1032,7 +1038,7 @@
                 new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
-            mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+            mTrustManager.unlockedByBiometricForUser(userId, FACE);
         }
         // Don't send cancel if authentication succeeds
         mFaceCancelSignal = null;
@@ -1043,7 +1049,7 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricAuthenticated(userId,
-                        BiometricSourceType.FACE,
+                        FACE,
                         isStrongBiometric);
             }
         }
@@ -1065,7 +1071,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+                cb.onBiometricAuthFailed(FACE);
             }
         }
         handleFaceHelp(BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
@@ -1078,7 +1084,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+                cb.onBiometricAcquired(FACE, acquireInfo);
             }
         }
     }
@@ -1113,7 +1119,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+                cb.onBiometricHelp(msgId, helpString, FACE);
             }
         }
     }
@@ -1181,12 +1187,12 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricError(msgId, errString,
-                        BiometricSourceType.FACE);
+                        FACE);
             }
         }
 
         if (lockedOutStateChanged) {
-            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+            notifyLockedOutStateChanged(FACE);
         }
     }
 
@@ -1200,7 +1206,7 @@
                 FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
 
         if (changed) {
-            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+            notifyLockedOutStateChanged(FACE);
         }
     }
 
@@ -1223,7 +1229,7 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
-                        BiometricSourceType.FACE);
+                        FACE);
             }
         }
     }
@@ -1364,7 +1370,39 @@
     }
 
     public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
-        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
+        // StrongAuthTracker#isUnlockingWithBiometricAllowed includes
+        // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
+        // however the strong auth tracker does not include the temporary lockout
+        // mFingerprintLockedOut.
+        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
+                && !mFingerprintLockedOut;
+    }
+
+    private boolean isUnlockingWithFaceAllowed() {
+        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(false);
+    }
+
+    /**
+     * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker
+     * and temporary lockout state (tracked by FingerprintManager via error codes).
+     */
+    public boolean isUnlockingWithFingerprintAllowed() {
+        return isUnlockingWithBiometricAllowed(true);
+    }
+
+    /**
+     * Whether the given biometric is allowed based on strongAuth & lockout states.
+     */
+    public boolean isUnlockingWithBiometricAllowed(
+            @NonNull BiometricSourceType biometricSourceType) {
+        switch (biometricSourceType) {
+            case FINGERPRINT:
+                return isUnlockingWithFingerprintAllowed();
+            case FACE:
+                return isUnlockingWithFaceAllowed();
+            default:
+                return false;
+        }
     }
 
     public boolean isUserInLockdown(int userId) {
@@ -1386,11 +1424,6 @@
         return isEncrypted || isLockDown;
     }
 
-    public boolean userNeedsStrongAuth() {
-        return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
-                != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-    }
-
     private boolean containsFlag(int haystack, int needle) {
         return (haystack & needle) != 0;
     }
@@ -1560,12 +1593,6 @@
         }
     };
 
-    private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
-            = (sensorId, userId, isStrongBiometric) -> {
-                // Trigger the fingerprint success path so the bouncer can be shown
-                handleFingerprintAuthenticated(userId, isStrongBiometric);
-            };
-
     /**
      * Propagates a pointer down event to keyguard.
      */
@@ -2636,27 +2663,25 @@
                         && (!mKeyguardGoingAway || !mDeviceInteractive)
                         && mIsPrimaryUser
                         && biometricEnabledForUser;
-
-        final boolean shouldListenBouncerState = !(mFingerprintLockedOut
-                && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted);
-
-        final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+        final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+        final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
+        final boolean shouldListenBouncerState =
+                !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
 
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
-                && !isEncryptedOrLockdownForUser
+                && !strongerAuthRequired
                 && userDoesNotHaveTrust);
 
         boolean shouldListenSideFpsState = true;
-        if (isSfpsSupported() && isSfpsEnrolled()) {
+        if (isSideFps) {
             shouldListenSideFpsState =
                     mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
         }
 
         boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
-                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+                && shouldListenBouncerState && shouldListenUdfpsState
                 && shouldListenSideFpsState;
-
         maybeLogListenerModelData(
                 new KeyguardFingerprintListenModel(
                     System.currentTimeMillis(),
@@ -2668,7 +2693,6 @@
                     mCredentialAttempted,
                     mDeviceInteractive,
                     mIsDreaming,
-                    isEncryptedOrLockdownForUser,
                     fingerprintDisabledForUser,
                     mFingerprintLockedOut,
                     mGoingToSleep,
@@ -2679,6 +2703,7 @@
                     mIsPrimaryUser,
                     shouldListenSideFpsState,
                     shouldListenForFingerprintAssistant,
+                    strongerAuthRequired,
                     mSwitchingUser,
                     isUdfps,
                     userDoesNotHaveTrust));
@@ -2706,10 +2731,7 @@
         final boolean isEncryptedOrTimedOut =
                 containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
                         || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
-
-        // TODO: always disallow when fp is already locked out?
-        final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
-
+        final boolean fpLockedOut = isFingerprintLockedOut();
         final boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
         // There's no reason to ask the HAL for authentication when the user can dismiss the
@@ -2831,15 +2853,22 @@
             // Waiting for restart via handleFingerprintError().
             return;
         }
-        mLogger.v("startListeningForFingerprint()");
 
         if (unlockPossible) {
             mFingerprintCancelSignal = new CancellationSignal();
 
-            if (isEncryptedOrLockdown(userId)) {
-                mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+            if (!isUnlockingWithFingerprintAllowed()) {
+                mLogger.v("startListeningForFingerprint - detect");
+                mFpm.detectFingerprint(
+                        mFingerprintCancelSignal,
+                        (sensorId, user, isStrongBiometric) -> {
+                            mLogger.d("fingerprint detected");
+                            // Trigger the fingerprint success path so the bouncer can be shown
+                            handleFingerprintAuthenticated(user, isStrongBiometric);
+                        },
                         userId);
             } else {
+                mLogger.v("startListeningForFingerprint - authenticate");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback, null /* handler */,
                         FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */);
@@ -3056,11 +3085,15 @@
             }
         }
 
+        // Immediately stop previous biometric listening states.
+        // Resetting lockout states updates the biometric listening states.
         if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+            stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
             handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
                     mFaceSensorProperties.get(0).sensorId, userId));
         }
         if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+            stopListeningForFingerprint();
             handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
                     mFingerprintSensorProperties.get(0).sensorId, userId));
         }
@@ -3467,7 +3500,7 @@
     @AnyThread
     public void setSwitchingUser(boolean switching) {
         mSwitchingUser = switching;
-        // Since this comes in on a binder thread, we need to post if first
+        // Since this comes in on a binder thread, we need to post it first
         mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_USER_SWITCHING));
     }
@@ -3559,8 +3592,8 @@
         Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
         mUserFaceAuthenticated.clear();
-        mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser);
-        mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser);
+        mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
+        mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
         mLogger.d("clearBiometricRecognized");
 
         for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index d60cc75..50449b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
     private final AccessibilityManager mA11yManager;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
+    private final ShadeController mShadeController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
     @Inject
     public SystemActions(Context context,
             NotificationShadeWindowController notificationShadeController,
+            ShadeController shadeController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional) {
         mContext = context;
+        mShadeController = shadeController;
         mRecentsOptional = recentsOptional;
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -529,9 +533,7 @@
     }
 
     private void handleAccessibilityDismissNotificationShade() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                centralSurfaces -> centralSurfaces.animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+        mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index b2a2a67..b962cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -107,6 +107,8 @@
         if (shouldAnimateForTransition(lastState, newState)) {
             iconView.playAnimation()
             iconViewOverlay.playAnimation()
+        } else if (lastState == STATE_IDLE && newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+            iconView.playAnimation()
         }
         LottieColorUtils.applyDynamicColors(context, iconView)
         LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index fc5f447..6ac54fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -116,9 +116,9 @@
         notificationShadeWindowController.setForcePluginOpen(false, this)
     }
 
-    fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
+    fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
         if (!keyguardStateController.isShowing ||
-            keyguardUpdateMonitor.userNeedsStrongAuth()) {
+                !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) {
             return
         }
 
@@ -246,7 +246,7 @@
         object : KeyguardUpdateMonitorCallback() {
             override fun onBiometricAuthenticated(
                 userId: Int,
-                biometricSourceType: BiometricSourceType?,
+                biometricSourceType: BiometricSourceType,
                 isStrongBiometric: Boolean
             ) {
                 if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
@@ -255,14 +255,14 @@
                 showUnlockRipple(biometricSourceType)
             }
 
-        override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+        override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
             if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
                 mView.retractDwellRipple()
             }
         }
 
         override fun onBiometricAcquired(
-            biometricSourceType: BiometricSourceType?,
+            biometricSourceType: BiometricSourceType,
             acquireInfo: Int
         ) {
             if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 1e35958..3e1c4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PointF;
@@ -28,6 +29,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.AttributeSet;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
 import androidx.annotation.NonNull;
@@ -68,25 +70,29 @@
     private boolean mShouldShowTipHint = false;
     private boolean mShouldShowEdgeHint = false;
 
-    UdfpsEnrollDrawable(@NonNull Context context) {
+    private int mEnrollIcon;
+    private int mMovingTargetFill;
+
+    UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context);
 
+        loadResources(context, attrs);
         mSensorOutlinePaint = new Paint(0 /* flags */);
         mSensorOutlinePaint.setAntiAlias(true);
-        mSensorOutlinePaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
+        mSensorOutlinePaint.setColor(mMovingTargetFill);
         mSensorOutlinePaint.setStyle(Paint.Style.FILL);
 
         mBlueFill = new Paint(0 /* flags */);
         mBlueFill.setAntiAlias(true);
-        mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill));
+        mBlueFill.setColor(mMovingTargetFill);
         mBlueFill.setStyle(Paint.Style.FILL);
 
         mMovingTargetFpIcon = context.getResources()
                 .getDrawable(R.drawable.ic_kg_fingerprint, null);
-        mMovingTargetFpIcon.setTint(context.getColor(R.color.udfps_enroll_icon));
+        mMovingTargetFpIcon.setTint(mEnrollIcon);
         mMovingTargetFpIcon.mutate();
 
-        getFingerprintDrawable().setTint(context.getColor(R.color.udfps_enroll_icon));
+        getFingerprintDrawable().setTint(mEnrollIcon);
 
         mTargetAnimListener = new Animator.AnimatorListener() {
             @Override
@@ -105,6 +111,16 @@
         };
     }
 
+    void loadResources(Context context, @Nullable AttributeSet attrs) {
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+                R.style.BiometricsEnrollStyle);
+        mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
+        mMovingTargetFill = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+        ta.recycle();
+    }
+
     void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
         mEnrollHelper = helper;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index af7e0b6..66a8424 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
@@ -26,6 +27,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -93,17 +95,25 @@
     @Nullable private ValueAnimator mCheckmarkAnimator;
     @NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
 
-    public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
+    private int mMovingTargetFill;
+    private int mMovingTargetFillError;
+    private int mEnrollProgress;
+    private int mEnrollProgressHelp;
+    private int mEnrollProgressHelpWithTalkback;
+
+    public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
         mContext = context;
+
+        loadResources(context, attrs);
         mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
-        mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+        mProgressColor = mEnrollProgress;
         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
         mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
-        mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
+        mOnFirstBucketFailedColor = mMovingTargetFillError;
         if (!mIsAccessibilityEnabled) {
-            mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+            mHelpColor = mEnrollProgressHelp;
         } else {
-            mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+            mHelpColor = mEnrollProgressHelpWithTalkback;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -111,7 +121,7 @@
 
         mBackgroundPaint = new Paint();
         mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
-        mBackgroundPaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
+        mBackgroundPaint.setColor(mMovingTargetFill);
         mBackgroundPaint.setAntiAlias(true);
         mBackgroundPaint.setStyle(Paint.Style.STROKE);
         mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
@@ -147,6 +157,23 @@
         };
     }
 
+    void loadResources(Context context, @Nullable AttributeSet attrs) {
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+                R.style.BiometricsEnrollStyle);
+        mMovingTargetFill = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+        mMovingTargetFillError = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
+        mEnrollProgress = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
+        mEnrollProgressHelp = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
+        mEnrollProgressHelpWithTalkback = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
+        ta.recycle();
+    }
+
     void onEnrollmentProgress(int remaining, int totalSteps) {
         mAfterFirstTouch = true;
         updateState(remaining, totalSteps, false /* showingHelp */);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 87be42c..1cc4141 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -47,8 +47,8 @@
 
     public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
-        mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
-        mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context);
+        mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
+        mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
         mHandler = new Handler(Looper.getMainLooper());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 4dfcd63..66e5d7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -30,6 +30,7 @@
 import android.service.controls.ControlsProviderService
 import androidx.annotation.WorkerThread
 import com.android.settingslib.applications.DefaultAppInfo
+import com.android.systemui.R
 import java.util.Objects
 
 class ControlsServiceInfo(
@@ -59,7 +60,8 @@
      * instead of using the controls rendered by SystemUI.
      *
      * The activity must be in the same package, exported, enabled and protected by the
-     * [Manifest.permission.BIND_CONTROLS] permission.
+     * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in
+     * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
      */
     var panelActivity: ComponentName? = null
         private set
@@ -70,6 +72,9 @@
     fun resolvePanelActivity() {
         if (resolved) return
         resolved = true
+        val validPackages = context.resources
+                .getStringArray(R.array.config_controlsPreferredPackages)
+        if (componentName.packageName !in validPackages) return
         panelActivity = _panelActivity?.let {
             val resolveInfos = mPm.queryIntentActivitiesAsUser(
                     Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e2f86bd..aa6c619 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -366,6 +366,11 @@
     @JvmField
     val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
 
+    // TODO(b/255854141): Tracking Bug
+    @JvmField
+    val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
+        unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = false)
+
     // 1300 - screenshots
     // TODO(b/254512719): Tracking Bug
     @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
@@ -386,7 +391,7 @@
     // 1600 - accessibility
     @JvmField
     val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
-        unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
+        unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations", teamfood = true)
 
     // 1700 - clipboard
     @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 0214313..e631816 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -220,6 +220,7 @@
                                 synchronized (mFinishCallbacks) {
                                     if (mFinishCallbacks.remove(transition) == null) return;
                                 }
+                                info.releaseAllSurfaces();
                                 Slog.d(TAG, "Finish IRemoteAnimationRunner.");
                                 finishCallback.onTransitionFinished(null /* wct */, null /* t */);
                             }
@@ -235,6 +236,8 @@
                     synchronized (mFinishCallbacks) {
                         origFinishCB = mFinishCallbacks.remove(transition);
                     }
+                    info.releaseAllSurfaces();
+                    t.close();
                     if (origFinishCB == null) {
                         // already finished (or not started yet), so do nothing.
                         return;
@@ -423,12 +426,15 @@
             t.apply();
             mBinder.setOccluded(true /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
@@ -440,12 +446,15 @@
             t.apply();
             mBinder.setOccluded(false /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5ed3ba7..948239a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -870,7 +870,7 @@
                 @Override
                 public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
-                        mCentralSurfaces.instantCollapseNotificationPanel();
+                        mShadeController.get().instantCollapseShade();
                     }
 
                     mOccludeAnimationPlaying = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index f5220b8..73dbeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -25,6 +25,7 @@
 object BuiltInKeyguardQuickAffordanceKeys {
     // Please keep alphabetical order of const names to simplify future maintenance.
     const val CAMERA = "camera"
+    const val FLASHLIGHT = "flashlight"
     const val HOME_CONTROLS = "home"
     const val QR_CODE_SCANNER = "qr_code_scanner"
     const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
new file mode 100644
index 0000000..49527d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -0,0 +1,144 @@
+/*
+ *  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.data.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+@SysUISingleton
+class FlashlightQuickAffordanceConfig @Inject constructor(
+        @Application private val context: Context,
+        private val flashlightController: FlashlightController,
+) : KeyguardQuickAffordanceConfig {
+
+    private sealed class FlashlightState {
+
+        abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
+
+        object On: FlashlightState() {
+            override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    Icon.Resource(
+                        R.drawable.ic_flashlight_on,
+                        ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+                    ),
+                    ActivationState.Active
+                )
+        }
+
+        object OffAvailable: FlashlightState() {
+            override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    Icon.Resource(
+                        R.drawable.ic_flashlight_off,
+                        ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+                    ),
+                    ActivationState.Inactive
+                )
+        }
+
+        object Unavailable: FlashlightState() {
+            override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+        }
+    }
+
+    override val key: String
+        get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT
+
+    override val pickerName: String
+        get() = context.getString(R.string.quick_settings_flashlight_label)
+
+    override val pickerIconResourceId: Int
+        get() = if (flashlightController.isEnabled) {
+            R.drawable.ic_flashlight_on
+        } else {
+            R.drawable.ic_flashlight_off
+        }
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+            conflatedCallbackFlow {
+        val flashlightCallback = object : FlashlightController.FlashlightListener {
+            override fun onFlashlightChanged(enabled: Boolean) {
+                trySendWithFailureLogging(
+                    if (enabled) {
+                        FlashlightState.On.toLockScreenState()
+                    } else {
+                        FlashlightState.OffAvailable.toLockScreenState()
+                    },
+                    TAG
+                )
+            }
+
+            override fun onFlashlightError() {
+                trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG)
+            }
+
+            override fun onFlashlightAvailabilityChanged(available: Boolean) {
+                trySendWithFailureLogging(
+                    if (!available) {
+                        FlashlightState.Unavailable.toLockScreenState()
+                    } else {
+                        if (flashlightController.isEnabled) {
+                            FlashlightState.On.toLockScreenState()
+                        } else {
+                            FlashlightState.OffAvailable.toLockScreenState()
+                        }
+                    },
+                    TAG
+                )
+            }
+        }
+
+        flashlightController.addCallback(flashlightCallback)
+
+        awaitClose {
+            flashlightController.removeCallback(flashlightCallback)
+        }
+    }
+
+    override fun onTriggered(expandable: Expandable?):
+            KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        flashlightController
+                .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled)
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+    }
+
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+        if (flashlightController.isAvailable) {
+            KeyguardQuickAffordanceConfig.PickerScreenState.Default
+        } else {
+            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+
+    companion object {
+        private const val TAG = "FlashlightQuickAffordanceConfig"
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index f7225a2..3013227c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -26,6 +26,7 @@
     @Provides
     @ElementsIntoSet
     fun quickAffordanceConfigs(
+        flashlight: FlashlightQuickAffordanceConfig,
         home: HomeControlsKeyguardQuickAffordanceConfig,
         quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
         qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
@@ -33,6 +34,7 @@
     ): Set<KeyguardQuickAffordanceConfig> {
         return setOf(
             camera,
+            flashlight,
             home,
             quickAccessWallet,
             qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 84a8074..2cf5fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.Trace
 import android.os.UserHandle
@@ -71,7 +72,7 @@
                 KeyguardUpdateMonitor.getCurrentUser()
             ) &&
             !needsFullscreenBouncer() &&
-            !keyguardUpdateMonitor.userNeedsStrongAuth() &&
+            keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
             !keyguardBypassController.bypassEnabled
 
     /** Runnable to show the primary bouncer. */
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index f9e341c..d6e29e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.log
 
-import android.app.ActivityManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogcatEchoTracker
 
@@ -29,15 +29,6 @@
     private val dumpManager: DumpManager,
     private val logcatEchoTracker: LogcatEchoTracker
 ) {
-    /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
-    private fun adjustMaxSize(requestedMaxSize: Int): Int {
-        return if (ActivityManager.isLowRamDeviceStatic()) {
-            minOf(requestedMaxSize, 20) /* low ram max log size*/
-        } else {
-            requestedMaxSize
-        }
-    }
-
     @JvmOverloads
     fun create(
         name: String,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
new file mode 100644
index 0000000..619eac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.log
+
+import android.app.ActivityManager
+
+class LogBufferHelper {
+    companion object {
+        /** If necessary, returns a limited maximum size for low ram (Go) devices */
+        fun adjustMaxSize(requestedMaxSize: Int): Int {
+            return if (ActivityManager.isLowRamDeviceStatic()) {
+                minOf(requestedMaxSize, 20) /* low ram max log size*/
+            } else {
+                requestedMaxSize
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
new file mode 100644
index 0000000..c27bfa3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.log.table
+
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface that enables logging the difference between values in table format.
+ *
+ * Many objects that we want to log are data-y objects with a collection of fields. When logging
+ * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
+ * highlight changes in individual fields.
+ *
+ * See [TableLogBuffer].
+ */
+interface Diffable<T> {
+    /**
+     * Finds the differences between [prevVal] and [this] and logs those diffs to [row].
+     *
+     * Each implementer should determine which individual fields have changed between [prevVal] and
+     * [this], and only log the fields that have actually changed. This helps save buffer space.
+     *
+     * For example, if:
+     * - prevVal = Object(val1=100, val2=200, val3=300)
+     * - this = Object(val1=100, val2=200, val3=333)
+     *
+     * Then only the val3 change should be logged.
+     */
+    fun logDiffs(prevVal: T, row: TableRowLogger)
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    initialValue: T,
+): Flow<T> {
+    return this.pairwiseBy(initialValue) { prevVal, newVal ->
+        tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
+        newVal
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
new file mode 100644
index 0000000..68c297f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.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.systemui.log.table
+
+/**
+ * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
+ *
+ * Each message represents a change to exactly 1 type, specified by [DataType].
+ */
+data class TableChange(
+    var timestamp: Long = 0,
+    var columnPrefix: String = "",
+    var columnName: String = "",
+    var type: DataType = DataType.EMPTY,
+    var bool: Boolean = false,
+    var int: Int = 0,
+    var str: String? = null,
+) {
+    /** Resets to default values so that the object can be recycled. */
+    fun reset(timestamp: Long, columnPrefix: String, columnName: String) {
+        this.timestamp = timestamp
+        this.columnPrefix = columnPrefix
+        this.columnName = columnName
+        this.type = DataType.EMPTY
+        this.bool = false
+        this.int = 0
+        this.str = null
+    }
+
+    /** Sets this to store a string change. */
+    fun set(value: String?) {
+        type = DataType.STRING
+        str = value
+    }
+
+    /** Sets this to store a boolean change. */
+    fun set(value: Boolean) {
+        type = DataType.BOOLEAN
+        bool = value
+    }
+
+    /** Sets this to store an int change. */
+    fun set(value: Int) {
+        type = DataType.INT
+        int = value
+    }
+
+    /** Returns true if this object has a change. */
+    fun hasData(): Boolean {
+        return columnName.isNotBlank() && type != DataType.EMPTY
+    }
+
+    fun getName(): String {
+        return if (columnPrefix.isNotBlank()) {
+            "$columnPrefix.$columnName"
+        } else {
+            columnName
+        }
+    }
+
+    fun getVal(): String {
+        return when (type) {
+            DataType.EMPTY -> null
+            DataType.STRING -> str
+            DataType.INT -> int
+            DataType.BOOLEAN -> bool
+        }.toString()
+    }
+
+    enum class DataType {
+        STRING,
+        BOOLEAN,
+        INT,
+        EMPTY,
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
new file mode 100644
index 0000000..429637a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.log.table
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A logger that logs changes in table format.
+ *
+ * Some parts of System UI maintain a lot of pieces of state at once.
+ * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ *
+ * - 10-10 10:10:10.456: state2 updated to newVal2
+ * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
+ * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
+ * - 10-10 10:11:05.123: state1 updated to newVal1
+ * - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3)
+ *
+ * However, it can sometimes be more useful to view the state changes in table format:
+ *
+ * - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2
+ * - -------------------------------------------------------------------------
+ * - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0-----------
+ * - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0-----------
+ * - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1-----------
+ * - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3-----------
+ *
+ * This class enables easy logging of the state changes in both change event format and table
+ * format.
+ *
+ * This class also enables easy logging of states that are a collection of fields. For example,
+ * stateN in the above example consists of two fields -- val1 and val2. It's useful to put each
+ * field into its own column so that ABT (Android Bug Tool) can easily highlight changes to
+ * individual fields.
+ *
+ * How it works:
+ *
+ * 1) Create an instance of this buffer via [TableLogBufferFactory].
+ *
+ * 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to
+ * only log the fields that have *changed* since the previous update, instead of always logging all
+ * fields.
+ *
+ * 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a
+ * [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any
+ * time your flow emits a new value.
+ *
+ * When a dump occurs, there will be two dumps:
+ *
+ * 1) The change events under the dumpable name "$name-changes".
+ *
+ * 2) This class will coalesce all the diffs into a table format and log them under the dumpable
+ * name "$name-table".
+ *
+ * @param maxSize the maximum size of the buffer. Must be > 0.
+ */
+class TableLogBuffer(
+    maxSize: Int,
+    private val name: String,
+    private val systemClock: SystemClock,
+) {
+    init {
+        if (maxSize <= 0) {
+            throw IllegalArgumentException("maxSize must be > 0")
+        }
+    }
+
+    private val buffer = RingBuffer(maxSize) { TableChange() }
+
+    // A [TableRowLogger] object, re-used each time [logDiffs] is called.
+    // (Re-used to avoid object allocation.)
+    private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
+
+    /**
+     * Log the differences between [prevVal] and [newVal].
+     *
+     * The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs.
+     *
+     * @param columnPrefix a prefix that will be applied to every column name that gets logged. This
+     * ensures that all the columns related to the same state object will be grouped together in the
+     * table.
+     */
+    @Synchronized
+    fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) {
+        val row = tempRow
+        row.timestamp = systemClock.currentTimeMillis()
+        row.columnPrefix = columnPrefix
+        newVal.logDiffs(prevVal, row)
+    }
+
+    // Keep these individual [logChange] methods private (don't let clients give us their own
+    // timestamps.)
+
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
+        val change = obtain(timestamp, prefix, columnName)
+        change.set(value)
+    }
+
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
+        val change = obtain(timestamp, prefix, columnName)
+        change.set(value)
+    }
+
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+        val change = obtain(timestamp, prefix, columnName)
+        change.set(value)
+    }
+
+    // TODO(b/259454430): Add additional change types here.
+
+    @Synchronized
+    private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
+        val tableChange = buffer.advance()
+        tableChange.reset(timestamp, prefix, columnName)
+        return tableChange
+    }
+
+    /**
+     * Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be
+     * dumped.
+     *
+     * This will be automatically called in [TableLogBufferFactory.create].
+     */
+    fun registerDumpables(dumpManager: DumpManager) {
+        dumpManager.registerNormalDumpable("$name-changes", changeDumpable)
+        dumpManager.registerNormalDumpable("$name-table", tableDumpable)
+    }
+
+    private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) }
+    private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) }
+
+    /** Dumps the list of [TableChange] objects. */
+    @Synchronized
+    @VisibleForTesting
+    fun dumpChanges(pw: PrintWriter) {
+        for (i in 0 until buffer.size) {
+            buffer[i].dump(pw)
+        }
+    }
+
+    /** Dumps an individual [TableChange]. */
+    private fun TableChange.dump(pw: PrintWriter) {
+        if (!this.hasData()) {
+            return
+        }
+        val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
+        pw.print(formattedTimestamp)
+        pw.print(" ")
+        pw.print(this.getName())
+        pw.print("=")
+        pw.print(this.getVal())
+        pw.println()
+    }
+
+    /**
+     * Coalesces all the [TableChange] objects into a table of values of time and dumps the table.
+     */
+    // TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to
+    //   fail and/or not dump anything else. We should move this processing to ABT (Android Bug
+    //   Tool), where we have unlimited time to process.
+    @Synchronized
+    @VisibleForTesting
+    fun dumpTable(pw: PrintWriter) {
+        val messages = buffer.iterator().asSequence().toList()
+
+        if (messages.isEmpty()) {
+            return
+        }
+
+        // Step 1: Create list of column headers
+        val headerSet = mutableSetOf<String>()
+        messages.forEach { headerSet.add(it.getName()) }
+        val headers: MutableList<String> = headerSet.toList().sorted().toMutableList()
+        headers.add(0, "timestamp")
+
+        // Step 2: Create a list with the current values for each column. Will be updated with each
+        // change.
+        val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE }
+
+        // Step 3: For each message, make the correct update to [currentRow] and save it to [rows].
+        val columnIndices: Map<String, Int> =
+            headers.mapIndexed { index, headerName -> headerName to index }.toMap()
+        val allRows = mutableListOf<List<String>>()
+
+        messages.forEach {
+            if (!it.hasData()) {
+                return@forEach
+            }
+
+            val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp)
+            if (formattedTimestamp != currentRow[0]) {
+                // The timestamp has updated, so save the previous row and continue to the next row
+                allRows.add(currentRow.toList())
+                currentRow[0] = formattedTimestamp
+            }
+            val columnIndex = columnIndices[it.getName()]!!
+            currentRow[columnIndex] = it.getVal()
+        }
+        // Add the last row
+        allRows.add(currentRow.toList())
+
+        // Step 4: Dump the rows
+        DumpsysTableLogger(
+                name,
+                headers,
+                allRows,
+            )
+            .printTableData(pw)
+    }
+
+    /**
+     * A private implementation of [TableRowLogger].
+     *
+     * Used so that external clients can't modify [timestamp].
+     */
+    private class TableRowLoggerImpl(
+        var timestamp: Long,
+        var columnPrefix: String,
+        val tableLogBuffer: TableLogBuffer,
+    ) : TableRowLogger {
+        /** Logs a change to a string value. */
+        override fun logChange(columnName: String, value: String?) {
+            tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+        }
+
+        /** Logs a change to a boolean value. */
+        override fun logChange(columnName: String, value: Boolean) {
+            tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+        }
+
+        /** Logs a change to an int value. */
+        override fun logChange(columnName: String, value: Int) {
+            tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+        }
+    }
+}
+
+val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+private const val DEFAULT_COLUMN_VALUE = "UNKNOWN"
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
new file mode 100644
index 0000000..f1f906f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.log.table
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+@SysUISingleton
+class TableLogBufferFactory
+@Inject
+constructor(
+    private val dumpManager: DumpManager,
+    private val systemClock: SystemClock,
+) {
+    fun create(
+        name: String,
+        maxSize: Int,
+    ): TableLogBuffer {
+        val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
+        tableBuffer.registerDumpables(dumpManager)
+        return tableBuffer
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
new file mode 100644
index 0000000..a7ba13b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
@@ -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.systemui.log.table
+
+/**
+ * A class that logs a row to [TableLogBuffer].
+ *
+ * Objects that implement [Diffable] will receive an instance of this class, and can log any changes
+ * to individual fields using the [logChange] methods. All logged changes will be associated with
+ * the same timestamp.
+ */
+interface TableRowLogger {
+    /** Logs a change to a string value. */
+    fun logChange(columnName: String, value: String?)
+
+    /** Logs a change to a boolean value. */
+    fun logChange(columnName: String, value: Boolean)
+
+    /** Logs a change to an int value. */
+    fun logChange(columnName: String, value: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 22f91f3..bfa67a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -109,7 +109,6 @@
         CharSequence dialogTitle = null;
         String appName = null;
         if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
-            // TODO(b/253438807): handle special app name
             dialogText = getString(R.string.media_projection_dialog_service_text);
             dialogTitle = getString(R.string.media_projection_dialog_service_title);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index cbb670e..f7a9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -799,6 +799,16 @@
         }
 
         if (
+            desiredLocation == LOCATION_QS &&
+                previousLocation == LOCATION_LOCKSCREEN &&
+                statusbarState == StatusBarState.SHADE
+        ) {
+            // This is an invalid transition, can happen when tapping on home control and the UMO
+            // while being on landscape orientation in tablet.
+            return false
+        }
+
+        if (
             statusbarState == StatusBarState.KEYGUARD &&
                 (currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN)
         ) {
@@ -1043,18 +1053,9 @@
                     rootOverlay!!.add(mediaFrame)
                 } else {
                     val targetHost = getHost(newLocation)!!.hostView
-                    // When adding back to the host, let's make sure to reset the bounds.
-                    // Usually adding the view will trigger a layout that does this automatically,
-                    // but we sometimes suppress this.
+                    // This will either do a full layout pass and remeasure, or it will bypass
+                    // that and directly set the mediaFrame's bounds within the premeasured host.
                     targetHost.addView(mediaFrame)
-                    val left = targetHost.paddingLeft
-                    val top = targetHost.paddingTop
-                    mediaFrame.setLeftTopRightBottom(
-                        left,
-                        top,
-                        left + currentBounds.width(),
-                        top + currentBounds.height()
-                    )
 
                     if (mediaFrame.childCount > 0) {
                         val child = mediaFrame.getChildAt(0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bf3031..4feb984 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -420,7 +420,9 @@
      */
     fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
         traceSection("MediaViewController#getMeasurementsForState") {
-            val viewState = obtainViewState(hostState) ?: return null
+            // measurements should never factor in the squish fraction
+            val viewState =
+                obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
             measurement.measuredWidth = viewState.width
             measurement.measuredHeight = viewState.height
             return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
index e56ab99..c5a82ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -23,11 +23,15 @@
 class MediaProjectionPermissionDialog(
     context: Context?,
     private val onStartRecordingClicked: Runnable,
-    appName: String?
-) : BaseScreenSharePermissionDialog(context, createOptionList(), appName) {
+    private val appName: String?
+) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        setDialogTitle(R.string.media_projection_permission_dialog_title)
+        if (appName == null) {
+            setDialogTitle(R.string.media_projection_permission_dialog_system_service_title)
+        } else {
+            setDialogTitle(R.string.media_projection_permission_dialog_title)
+        }
         setStartButtonText(R.string.media_projection_permission_dialog_continue)
         setStartButtonOnClickListener {
             // Note that it is important to run this callback before dismissing, so that the
@@ -38,17 +42,30 @@
     }
 
     companion object {
-        private fun createOptionList(): List<ScreenShareOption> {
+        private fun createOptionList(appName: String?): List<ScreenShareOption> {
+            val singleAppWarningText =
+                if (appName == null) {
+                    R.string.media_projection_permission_dialog_system_service_warning_single_app
+                } else {
+                    R.string.media_projection_permission_dialog_warning_single_app
+                }
+            val entireScreenWarningText =
+                if (appName == null) {
+                    R.string.media_projection_permission_dialog_system_service_warning_entire_screen
+                } else {
+                    R.string.media_projection_permission_dialog_warning_entire_screen
+                }
+
             return listOf(
                 ScreenShareOption(
-                    ENTIRE_SCREEN,
-                    R.string.media_projection_permission_dialog_option_entire_screen,
-                    R.string.media_projection_permission_dialog_warning_entire_screen
+                    mode = ENTIRE_SCREEN,
+                    spinnerText = R.string.media_projection_permission_dialog_option_entire_screen,
+                    warningText = entireScreenWarningText
                 ),
                 ScreenShareOption(
-                    SINGLE_APP,
-                    R.string.media_projection_permission_dialog_option_single_app,
-                    R.string.media_projection_permission_dialog_warning_single_app
+                    mode = SINGLE_APP,
+                    spinnerText = R.string.media_projection_permission_dialog_option_single_app,
+                    warningText = singleAppWarningText
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8609e4a..57b256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -84,6 +84,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -279,6 +281,13 @@
     private final ActionIntentExecutor mActionExecutor;
     private final UserManager mUserManager;
 
+    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+        if (DEBUG_INPUT) {
+            Log.d(TAG, "Predictive Back callback dispatched");
+        }
+        respondToBack();
+    };
+
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
@@ -465,6 +474,10 @@
         }
     }
 
+    private void respondToBack() {
+        dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+    }
+
     /**
      * Update resources on configuration change. Reinflate for theme/color changes.
      */
@@ -476,6 +489,26 @@
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+        mScreenshotView.addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Registering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Unregistering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher()
+                                .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+                    }
+                });
         mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
@@ -503,7 +536,7 @@
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
                 }
-                dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                respondToBack();
                 return true;
             }
             return false;
@@ -972,13 +1005,8 @@
 
         if (imageData.uri != null) {
             if (!imageData.owner.equals(Process.myUserHandle())) {
-                // TODO: Handle non-primary user ownership (e.g. Work Profile)
-                // This image is owned by another user. Special treatment will be
-                // required in the UI (badging) as well as sending intents which can
-                // correctly forward those URIs on to be read (actions).
-
-                Log.d(TAG, "*** Screenshot saved to a non-primary user ("
-                        + imageData.owner + ") as " + imageData.uri);
+                Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
+                        + imageData.uri);
             }
             mScreenshotHandler.post(() -> {
                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
@@ -1059,6 +1087,11 @@
                     R.string.screenshot_failed_to_save_text);
         } else {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+            if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                    && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
+                        mPackageName);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 31e4464..5e47d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -19,6 +19,7 @@
 import android.annotation.IdRes
 import android.app.StatusBarManager
 import android.content.res.Configuration
+import android.os.Bundle
 import android.os.Trace
 import android.os.Trace.TRACE_TAG_APP
 import android.util.Pair
@@ -34,6 +35,8 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -53,6 +56,7 @@
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.VariableDateView
 import com.android.systemui.statusbar.policy.VariableDateViewController
@@ -89,7 +93,8 @@
     private val dumpManager: DumpManager,
     private val featureFlags: FeatureFlags,
     private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
-    private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+    private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
+    private val demoModeController: DemoModeController
 ) : ViewController<View>(header), Dumpable {
 
     companion object {
@@ -126,7 +131,7 @@
     private lateinit var qsCarrierGroupController: QSCarrierGroupController
 
     private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-    private val clock: TextView = header.findViewById(R.id.clock)
+    private val clock: Clock = header.findViewById(R.id.clock)
     private val date: TextView = header.findViewById(R.id.date)
     private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
     private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
@@ -212,6 +217,14 @@
         view.onApplyWindowInsets(insets)
     }
 
+    private val demoModeReceiver = object : DemoMode {
+        override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+        override fun dispatchDemoCommand(command: String, args: Bundle) =
+            clock.dispatchDemoCommand(command, args)
+        override fun onDemoModeStarted() = clock.onDemoModeStarted()
+        override fun onDemoModeFinished() = clock.onDemoModeFinished()
+    }
+
     private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
         override fun onChipVisibilityRefreshed(visible: Boolean) {
             if (header is MotionLayout) {
@@ -300,6 +313,7 @@
 
         dumpManager.registerDumpable(this)
         configurationController.addCallback(configurationControllerListener)
+        demoModeController.addCallback(demoModeReceiver)
 
         updateVisibility()
         updateTransition()
@@ -309,6 +323,7 @@
         privacyIconsController.chipVisibilityListener = null
         dumpManager.unregisterDumpable(this::class.java.simpleName)
         configurationController.removeCallback(configurationControllerListener)
+        demoModeController.removeCallback(demoModeReceiver)
     }
 
     fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -521,4 +536,7 @@
             updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
         }
     }
+
+    @VisibleForTesting
+    internal fun simulateViewDetached() = this.onViewDetached()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..de9dcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.shade;
 
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
  */
 public interface ShadeController {
 
-    /**
-     * Make our window larger and the panel expanded
-     */
-    void instantExpandNotificationsPanel();
+    /** Make our window larger and the shade expanded */
+    void instantExpandShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels();
+    /** Collapse the shade instantly with no animation. */
+    void instantCollapseShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags);
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade(int flags);
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeForced();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeDelayed();
 
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
-     * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+     * dismissing status bar when on {@link StatusBarState#SHADE}.
      */
-    void animateCollapsePanels(int flags, boolean force);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
     void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
 
     /**
-     * If the notifications panel is not fully expanded, collapse it animated.
+     * If the shade is not fully expanded, collapse it animated.
      *
      * @return Seems to always return false
      */
@@ -77,9 +81,7 @@
      */
     void addPostCollapseAction(Runnable action);
 
-    /**
-     * Run all of the runnables added by {@link #addPostCollapseAction}.
-     */
+    /** Run all of the runnables added by {@link #addPostCollapseAction}. */
     void runPostCollapseRunnables();
 
     /**
@@ -87,13 +89,48 @@
      *
      * @return true if the shade was open, else false
      */
-    boolean collapsePanel();
+    boolean collapseShade();
 
     /**
-     * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
-     * the panel. Post collapse runnables will be executed
+     * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+     * the shade. Post collapse runnables will be executed
      *
      * @param animate true to animate the collapse, false for instantaneous collapse
      */
-    void collapsePanel(boolean animate);
+    void collapseShade(boolean animate);
+
+    /** Makes shade expanded but not visible. */
+    void makeExpandedInvisible();
+
+    /** Makes shade expanded and visible. */
+    void makeExpandedVisible(boolean force);
+
+    /** Returns whether the shade is expanded and visible. */
+    boolean isExpandedVisible();
+
+    /** Handle status bar touch event. */
+    void onStatusBarTouch(MotionEvent event);
+
+    /** Sets the listener for when the visibility of the shade changes. */
+    void setVisibilityListener(ShadeVisibilityListener listener);
+
+    /** */
+    void setNotificationPresenter(NotificationPresenter presenter);
+
+    /** */
+    void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+    /** */
+    void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController);
+
+    /** Listens for shade visibility changes. */
+    interface ShadeVisibilityListener {
+        /** Called when the visibility of the shade changes. */
+        void visibilityChanged(boolean visible);
+
+        /** Called when shade expanded and visible state changed. */
+        void expandedVisibleChanged(boolean expandedVisible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d783293..807e2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.shade;
 
+import android.content.ComponentCallbacks2;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -39,68 +43,81 @@
 
 /** An implementation of {@link ShadeController}. */
 @SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
 
     private static final String TAG = "ShadeControllerImpl";
     private static final boolean SPEW = false;
 
-    private final CommandQueue mCommandQueue;
-    private final StatusBarStateController mStatusBarStateController;
-    protected final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final int mDisplayId;
-    protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+    private final CommandQueue mCommandQueue;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final StatusBarWindowController mStatusBarWindowController;
+
     private final Lazy<AssistManager> mAssistManagerLazy;
+    private final Lazy<NotificationGutsManager> mGutsManager;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
+    private boolean mExpandedVisible;
+
+    private NotificationPanelViewController mNotificationPanelViewController;
+    private NotificationPresenter mPresenter;
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private ShadeVisibilityListener mShadeVisibilityListener;
+
     @Inject
     public ShadeControllerImpl(
             CommandQueue commandQueue,
+            KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
-            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             WindowManager windowManager,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            Lazy<AssistManager> assistManagerLazy
+            Lazy<AssistManager> assistManagerLazy,
+            Lazy<NotificationGutsManager> gutsManager
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
+        mStatusBarWindowController = statusBarWindowController;
+        mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
-        // TODO: Remove circular reference to CentralSurfaces when possible.
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mKeyguardStateController = keyguardStateController;
         mAssistManagerLazy = assistManagerLazy;
     }
 
     @Override
-    public void instantExpandNotificationsPanel() {
+    public void instantExpandShade() {
         // Make our window larger and the panel expanded.
-        getCentralSurfaces().makeExpandedVisible(true /* force */);
-        getNotificationPanelViewController().expand(false /* animate */);
+        makeExpandedVisible(true /* force */);
+        mNotificationPanelViewController.expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
-    public void animateCollapsePanels() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+    public void animateCollapseShade() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     @Override
-    public void animateCollapsePanels(int flags) {
-        animateCollapsePanels(flags, false /* force */, false /* delayed */,
-                1.0f /* speedUpFactor */);
+    public void animateCollapseShade(int flags) {
+        animateCollapsePanels(flags, false, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeForced() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
-        animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeDelayed() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
     }
 
     @Override
@@ -111,34 +128,26 @@
             return;
         }
         if (SPEW) {
-            Log.d(TAG, "animateCollapse():"
-                    + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
-                    + " flags=" + flags);
+            Log.d(TAG,
+                    "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
         }
-
-        // TODO(b/62444020): remove when this bug is fixed
-        Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
-                + " canPanelBeCollapsed(): "
-                + getNotificationPanelViewController().canPanelBeCollapsed());
         if (getNotificationShadeWindowView() != null
-                && getNotificationPanelViewController().canPanelBeCollapsed()
+                && mNotificationPanelViewController.canPanelBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
-            getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
-            getNotificationPanelViewController()
-                    .collapsePanel(true /* animate */, delayed, speedUpFactor);
+            mNotificationShadeWindowViewController.cancelExpandHelper();
+            mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
         }
     }
 
-
     @Override
     public boolean closeShadeIfOpen() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-            getCentralSurfaces().visibilityChanged(false);
+            notifyVisibilityChanged(false);
             mAssistManagerLazy.get().hideAssist();
         }
         return false;
@@ -146,21 +155,19 @@
 
     @Override
     public boolean isShadeOpen() {
-        NotificationPanelViewController controller =
-                getNotificationPanelViewController();
-        return controller.isExpanding() || controller.isFullyExpanded();
+        return mNotificationPanelViewController.isExpanding()
+                || mNotificationPanelViewController.isFullyExpanded();
     }
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        getNotificationPanelViewController().addOnGlobalLayoutListener(
+        mNotificationPanelViewController.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
-                        if (getCentralSurfaces().getNotificationShadeWindowView()
-                                .isVisibleToUser()) {
-                            getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
-                            getNotificationPanelViewController().postToView(executable);
+                        if (getNotificationShadeWindowView().isVisibleToUser()) {
+                            mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+                            mNotificationPanelViewController.postToView(executable);
                         }
                     }
                 });
@@ -183,12 +190,11 @@
     }
 
     @Override
-    public boolean collapsePanel() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+    public boolean collapseShade() {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             // close the shade if it was open
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                    true /* force */, true /* delayed */);
-            getCentralSurfaces().visibilityChanged(false);
+            animateCollapseShadeDelayed();
+            notifyVisibilityChanged(false);
 
             return true;
         } else {
@@ -197,33 +203,131 @@
     }
 
     @Override
-    public void collapsePanel(boolean animate) {
+    public void collapseShade(boolean animate) {
         if (animate) {
-            boolean willCollapse = collapsePanel();
+            boolean willCollapse = collapseShade();
             if (!willCollapse) {
                 runPostCollapseRunnables();
             }
-        } else if (!getPresenter().isPresenterFullyCollapsed()) {
-            getCentralSurfaces().instantCollapseNotificationPanel();
-            getCentralSurfaces().visibilityChanged(false);
+        } else if (!mPresenter.isPresenterFullyCollapsed()) {
+            instantCollapseShade();
+            notifyVisibilityChanged(false);
         } else {
             runPostCollapseRunnables();
         }
     }
 
-    private CentralSurfaces getCentralSurfaces() {
-        return mCentralSurfacesOptionalLazy.get().get();
+    @Override
+    public void onStatusBarTouch(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (mExpandedVisible) {
+                animateCollapseShade();
+            }
+        }
     }
 
-    private NotificationPresenter getPresenter() {
-        return getCentralSurfaces().getPresenter();
+    @Override
+    public void instantCollapseShade() {
+        mNotificationPanelViewController.instantCollapse();
+        runPostCollapseRunnables();
     }
 
-    protected NotificationShadeWindowView getNotificationShadeWindowView() {
-        return getCentralSurfaces().getNotificationShadeWindowView();
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+            return;
+        }
+
+        mExpandedVisible = true;
+
+        // Expand the window to encompass the full screen in anticipation of the drag.
+        // It's only possible to do atomically because the status bar is at the top of the screen!
+        mNotificationShadeWindowController.setPanelVisible(true);
+
+        notifyVisibilityChanged(true);
+        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+        notifyExpandedVisibleChanged(true);
     }
 
-    private NotificationPanelViewController getNotificationPanelViewController() {
-        return getCentralSurfaces().getNotificationPanelViewController();
+    @Override
+    public void makeExpandedInvisible() {
+        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+        if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+            return;
+        }
+
+        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+        mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+        mNotificationPanelViewController.closeQs();
+
+        mExpandedVisible = false;
+        notifyVisibilityChanged(false);
+
+        // Update the visibility of notification shade and status bar window.
+        mNotificationShadeWindowController.setPanelVisible(false);
+        mStatusBarWindowController.setForceStatusBarVisible(false);
+
+        // Close any guts that might be visible
+        mGutsManager.get().closeAndSaveGuts(
+                true /* removeLeavebehind */,
+                true /* force */,
+                true /* removeControls */,
+                -1 /* x */,
+                -1 /* y */,
+                true /* resetMenu */);
+
+        runPostCollapseRunnables();
+        notifyExpandedVisibleChanged(false);
+        mCommandQueue.recomputeDisableFlags(
+                mDisplayId,
+                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+        // the bouncer appear animation.
+        if (!mKeyguardStateController.isShowing()) {
+            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+        }
+    }
+
+    @Override
+    public boolean isExpandedVisible() {
+        return mExpandedVisible;
+    }
+
+    @Override
+    public void setVisibilityListener(ShadeVisibilityListener listener) {
+        mShadeVisibilityListener = listener;
+    }
+
+    private void notifyVisibilityChanged(boolean visible) {
+        mShadeVisibilityListener.visibilityChanged(visible);
+    }
+
+    private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+        mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+    }
+
+    @Override
+    public void setNotificationPresenter(NotificationPresenter presenter) {
+        mPresenter = presenter;
+    }
+
+    @Override
+    public void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController controller) {
+        mNotificationShadeWindowViewController = controller;
+    }
+
+    private NotificationShadeWindowView getNotificationShadeWindowView() {
+        return mNotificationShadeWindowViewController.getView();
+    }
+
+    @Override
+    public void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelViewController = notificationPanelViewController;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 143c697..bd5b8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -94,7 +94,11 @@
     private val views: Sequence<View>
         get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
 
-    private var showingListener: ShowingListener? = null
+    var showingListener: ShowingListener? = null
+        set(value) {
+            field = value
+        }
+        get() = field
 
     init {
         contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
@@ -147,10 +151,6 @@
         return uiExecutor
     }
 
-    fun setShowingListener(l: ShowingListener?) {
-        showingListener = l
-    }
-
     @UiThread
     fun setNewRotation(rot: Int) {
         dlog("updateRotation: $rot")
@@ -219,7 +219,7 @@
 
     // Update the gravity and margins of the privacy views
     @UiThread
-    private fun updateRotations(rotation: Int, paddingTop: Int) {
+    open fun updateRotations(rotation: Int, paddingTop: Int) {
         // To keep a view in the corner, its gravity is always the description of its current corner
         // Therefore, just figure out which view is in which corner. This turns out to be something
         // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -250,7 +250,7 @@
     }
 
     @UiThread
-    private fun setCornerSizes(state: ViewState) {
+    open fun setCornerSizes(state: ViewState) {
         // StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import java.util.Collections;
-
 import javax.inject.Inject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
         // If the user selected Priority and the previous selection was not priority, show a
         // People Tile add request.
         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
         mGutsContainer.closeControls(v, /* save= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 073bd4b..b519aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1401,10 +1401,10 @@
             mExpandedHeight = height;
             setIsExpanded(height > 0);
             int minExpansionHeight = getMinExpansionHeight();
-            if (height < minExpansionHeight) {
+            if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
                 mClipRect.left = 0;
                 mClipRect.right = getWidth();
-                mClipRect.top = getNotificationsClippingTopBound();
+                mClipRect.top = 0;
                 mClipRect.bottom = (int) height;
                 height = minExpansionHeight;
                 setRequestedClipBounds(mClipRect);
@@ -1466,17 +1466,6 @@
         notifyAppearChangedListeners();
     }
 
-    private int getNotificationsClippingTopBound() {
-        if (isHeadsUpTransition()) {
-            // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
-            // to give it extra clipping margin. Because clipping has rounded corners, we also
-            // need to account for that corner clipping.
-            return -mAmbientState.getStackTopMargin() - mCornerRadius;
-        } else {
-            return 0;
-        }
-    }
-
     private void notifyAppearChangedListeners() {
         float appear;
         float expandAmount;
@@ -4236,7 +4225,7 @@
                 mShadeNeedsToClose = false;
                 postDelayed(
                         () -> {
-                            mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                            mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
                         },
                         DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
             }
@@ -5139,6 +5128,7 @@
             println(pw, "intrinsicPadding", mIntrinsicPadding);
             println(pw, "topPadding", mTopPadding);
             println(pw, "bottomPadding", mBottomPadding);
+            mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
         pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
 import com.android.systemui.util.children
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
@@ -53,6 +54,8 @@
     @Main private val resources: Resources
 ) {
 
+    private lateinit var lastComputeHeightLog : String
+
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
         shelfIntrinsicHeight: Float
     ): Int {
         log { "\n" }
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+            /* computeHeight= */ false)
 
         var maxNotifications =
             stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
         shelfIntrinsicHeight: Float
     ): Float {
         log { "\n" }
+        lastComputeHeightLog = ""
         val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+                    /* computeHeight= */ true)
 
         val (notificationsHeight, shelfHeightWithSpaceBefore) =
             heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
                 heightPerMaxNotifications.last() // Height with all notifications visible.
             }
-        log {
-            "computeHeight(maxNotifications=$maxNotifications," +
+        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
                 "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
                 "${notificationsHeight + shelfHeightWithSpaceBefore}" +
                 " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+        log {
+            lastComputeHeightLog
         }
         return notificationsHeight + shelfHeightWithSpaceBefore
     }
@@ -184,7 +192,8 @@
 
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
-        shelfHeight: Float
+        shelfHeight: Float,
+        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
         log { "computeHeightPerNotificationLimit" }
 
@@ -213,9 +222,14 @@
                             currentIndex = firstViewInShelfIndex)
                     spaceBeforeShelf + shelfHeight
                 }
+
+            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+            if (computeHeight) {
+                lastComputeHeightLog += "\n" + currentLog
+            }
             log {
-                "i=$i notificationsHeight=$notifications " +
-                    "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+                currentLog
             }
             yield(
                 StackHeight(
@@ -260,6 +274,10 @@
         return size
     }
 
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+    }
+
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
         if (visibility == GONE || hasNoContentHeight()) return false
         if (onLockscreen) {
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 be08183..0ec7c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -122,6 +122,7 @@
         ActivityOptions options = getDefaultActivityOptions(animationAdapter);
         options.setLaunchDisplayId(displayId);
         options.setCallerDisplayId(displayId);
+        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
         return options.toBundle();
     }
 
@@ -145,6 +146,7 @@
                 : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
         options.setLaunchDisplayId(displayId);
         options.setCallerDisplayId(displayId);
+        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
         return options.toBundle();
     }
 
@@ -191,8 +193,6 @@
 
     void animateExpandSettingsPanel(@Nullable String subpanel);
 
-    void animateCollapsePanels(int flags, boolean force);
-
     void collapsePanelOnMainThread();
 
     void togglePanel();
@@ -280,8 +280,6 @@
 
     void postAnimateOpenPanels();
 
-    boolean isExpandedVisible();
-
     boolean isPanelExpanded();
 
     void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
@@ -493,12 +491,13 @@
 
     void updateNotificationPanelTouchState();
 
+    /**
+     * TODO(b/257041702) delete this
+     * @deprecated Use ShadeController#makeExpandedVisible
+     */
+    @Deprecated
     void makeExpandedVisible(boolean force);
 
-    void instantCollapseNotificationPanel();
-
-    void visibilityChanged(boolean visible);
-
     int getDisplayId();
 
     int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index f3482f4..6b72e96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -209,7 +209,7 @@
     public void animateExpandNotificationsPanel() {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -222,7 +222,7 @@
     public void animateExpandSettingsPanel(@Nullable String subPanel) {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -276,7 +276,7 @@
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -293,7 +293,7 @@
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
             mCentralSurfaces.updateQsExpansionEnabled();
             if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -550,7 +550,7 @@
     @Override
     public void togglePanel() {
         if (mCentralSurfaces.isPanelExpanded()) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         } else {
             animateExpandNotificationsPanel();
         }
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 5efd460..d988772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -406,12 +405,6 @@
 
     /** */
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        mCommandQueueCallbacks.animateCollapsePanels(flags, force);
-    }
-
-    /** */
-    @Override
     public void togglePanel() {
         mCommandQueueCallbacks.togglePanel();
     }
@@ -493,8 +486,6 @@
 
     private View mReportRejectedTouch;
 
-    private boolean mExpandedVisible;
-
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -893,6 +884,8 @@
         updateDisplaySize();
         mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
 
+        initShadeVisibilityListener();
+
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -977,6 +970,11 @@
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy.init();
 
+        // Based on teamfood flag, turn predictive back dispatch on at runtime.
+        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+            mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+        }
+
         mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onUnlockedChanged() {
@@ -1078,6 +1076,25 @@
                                 requestTopUi, componentTag))));
     }
 
+    @VisibleForTesting
+    void initShadeVisibilityListener() {
+        mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+            @Override
+            public void visibilityChanged(boolean visible) {
+                onShadeVisibilityChanged(visible);
+            }
+
+            @Override
+            public void expandedVisibleChanged(boolean expandedVisible) {
+                if (expandedVisible) {
+                    setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+                } else {
+                    onExpandedInvisible();
+                }
+            }
+        });
+    }
+
     private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
         Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
         onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1223,7 +1240,7 @@
 
         mNotificationPanelViewController.initDependencies(
                 this,
-                this::makeExpandedInvisible,
+                mShadeController::makeExpandedInvisible,
                 mNotificationShelfController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1426,6 +1443,7 @@
         mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
         mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+        mShadeController.setNotificationPresenter(mPresenter);
         mNotificationsController.initialize(
                 this,
                 mPresenter,
@@ -1475,11 +1493,7 @@
         return (v, event) -> {
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
-            if (event.getAction() == MotionEvent.ACTION_UP) {
-                if (mExpandedVisible) {
-                    mShadeController.animateCollapsePanels();
-                }
-            }
+            mShadeController.onStatusBarTouch(event);
             return mNotificationShadeWindowView.onTouchEvent(event);
         };
     }
@@ -1501,6 +1515,9 @@
         mNotificationShadeWindowViewController.setupExpandedStatusBar();
         mNotificationPanelViewController =
                 mCentralSurfacesComponent.getNotificationPanelViewController();
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
         mCentralSurfacesComponent.getLockIconViewController().init();
         mStackScrollerController =
                 mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1822,7 +1839,7 @@
                 && isLaunchForActivity) {
             onClosingFinished();
         } else {
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         }
     }
 
@@ -1833,7 +1850,7 @@
             onClosingFinished();
         }
         if (launchIsFullScreen) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
     }
 
@@ -1923,33 +1940,13 @@
     }
 
     @Override
-    public void makeExpandedVisible(boolean force) {
-        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
-            return;
-        }
-
-        mExpandedVisible = true;
-
-        // Expand the window to encompass the full screen in anticipation of the drag.
-        // This is only possible to do atomically because the status bar is at the top of the screen!
-        mNotificationShadeWindowController.setPanelVisible(true);
-
-        visibilityChanged(true);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
-        setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
-    }
-
-    @Override
     public void postAnimateCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapsePanels);
+        mMainExecutor.execute(mShadeController::animateCollapseShade);
     }
 
     @Override
     public void postAnimateForceCollapsePanels() {
-        mMainExecutor.execute(
-                () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
-                true /* force */));
+        mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
     }
 
     @Override
@@ -1958,11 +1955,6 @@
     }
 
     @Override
-    public boolean isExpandedVisible() {
-        return mExpandedVisible;
-    }
-
-    @Override
     public boolean isPanelExpanded() {
         return mPanelExpanded;
     }
@@ -1991,46 +1983,13 @@
         }
     }
 
-    void makeExpandedInvisible() {
-        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
-
-        if (!mExpandedVisible || mNotificationShadeWindowView == null) {
-            return;
-        }
-
-        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
-        mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
-                1.0f /* speedUpFactor */);
-
-        mNotificationPanelViewController.closeQs();
-
-        mExpandedVisible = false;
-        visibilityChanged(false);
-
-        // Update the visibility of notification shade and status bar window.
-        mNotificationShadeWindowController.setPanelVisible(false);
-        mStatusBarWindowController.setForceStatusBarVisible(false);
-
-        // Close any guts that might be visible
-        mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
-        mShadeController.runPostCollapseRunnables();
+    private void onExpandedInvisible() {
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
             showBouncerOrLockScreenIfKeyguard();
         } else if (DEBUG) {
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
-        mCommandQueue.recomputeDisableFlags(
-                mDisplayId,
-                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
-        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
-        // the bouncer appear animation.
-        if (!mKeyguardStateController.isShowing()) {
-            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-        }
     }
 
     /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@@ -2067,7 +2026,8 @@
             final boolean upOrCancel =
                     event.getAction() == MotionEvent.ACTION_UP ||
                     event.getAction() == MotionEvent.ACTION_CANCEL;
-            setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
+            setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
+                    !upOrCancel || mShadeController.isExpandedVisible());
         }
     }
 
@@ -2216,7 +2176,7 @@
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible);
+            pw.println("  mExpandedVisible=" + mShadeController.isExpandedVisible());
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2531,10 +2491,8 @@
                     }
                 }
                 if (dismissShade) {
-                    if (mExpandedVisible && !mBouncerShowing) {
-                        mShadeController.animateCollapsePanels(
-                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed*/);
+                    if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+                        mShadeController.animateCollapseShadeDelayed();
                     } else {
                         // Do it after DismissAction has been processed to conserve the needed
                         // ordering.
@@ -2576,7 +2534,7 @@
                             flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
                         }
                     }
-                    mShadeController.animateCollapsePanels(flags);
+                    mShadeController.animateCollapseShade(flags);
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 if (mNotificationShadeWindowController != null) {
@@ -2691,10 +2649,9 @@
                 com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
     }
 
-    // Visibility reporting
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onVisibleToUser();
             mNotificationLogger.startNotificationLogging();
 
             if (!mIsBackCallbackRegistered) {
@@ -2711,7 +2668,7 @@
             }
         } else {
             mNotificationLogger.stopNotificationLogging();
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onInvisibleToUser();
 
             if (mIsBackCallbackRegistered) {
                 ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2731,41 +2688,38 @@
         }
     }
 
-    // Visibility reporting
-    void handleVisibleToUserChangedImpl(boolean visibleToUser) {
-        if (visibleToUser) {
-            /* The LEDs are turned off when the notification panel is shown, even just a little bit.
-             * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
-             * this.
-             */
-            boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-            boolean clearNotificationEffects =
-                    !mPresenter.isPresenterFullyCollapsed() &&
-                            (mState == StatusBarState.SHADE
-                                    || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mNotificationsController.getActiveNotificationsCount();
-            if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
-                notificationLoad = 1;
-            }
-            final int finalNotificationLoad = notificationLoad;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelRevealed(clearNotificationEffects,
-                            finalNotificationLoad);
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
-        } else {
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelHidden();
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
+    void onVisibleToUser() {
+        /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+         * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+         * this.
+         */
+        boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+        boolean clearNotificationEffects =
+                !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+                        || mState == StatusBarState.SHADE_LOCKED);
+        int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+        if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+            notificationLoad = 1;
         }
+        final int finalNotificationLoad = notificationLoad;
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelRevealed(clearNotificationEffects,
+                        finalNotificationLoad);
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
+    }
 
+    void onInvisibleToUser() {
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelHidden();
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
     }
 
     private void logStateToEventlog() {
@@ -2943,7 +2897,7 @@
     private void updatePanelExpansionForKeyguard() {
         if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
@@ -3062,7 +3016,7 @@
             // too heavy for the CPU and GPU on any device.
             mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
         } else if (!mNotificationPanelViewController.isCollapsing()) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
 
         // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3220,8 +3174,7 @@
     @Override
     public boolean onMenuPressed() {
         if (shouldUnlockOnMenuPressed()) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3266,7 +3219,7 @@
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
                 && !isBouncerShowingOverDream()) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
             return true;
         }
@@ -3276,8 +3229,7 @@
     @Override
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3317,12 +3269,6 @@
         }
     }
 
-    @Override
-    public void instantCollapseNotificationPanel() {
-        mNotificationPanelViewController.instantCollapse();
-        mShadeController.runPostCollapseRunnables();
-    }
-
     /**
      * Collapse the panel directly if we are on the main thread, post the collapsing on the main
      * thread if we are not.
@@ -3330,9 +3276,9 @@
     @Override
     public void collapsePanelOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+            mContext.getMainExecutor().execute(mShadeController::collapseShade);
         }
     }
 
@@ -3472,7 +3418,7 @@
             mNotificationShadeWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         }
     }
 
@@ -3535,7 +3481,7 @@
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
             // we need to be expanded for it to be visible.
             if (mDozeParameters.shouldShowLightRevealScrim()) {
-                makeExpandedVisible(true);
+                mShadeController.makeExpandedVisible(true);
             }
 
             DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3564,7 +3510,7 @@
                 // If we are waking up during the screen off animation, we should undo making the
                 // expanded visible (we did that so the LightRevealScrim would be visible).
                 if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
-                    makeExpandedInvisible();
+                    mShadeController.makeExpandedInvisible();
                 }
 
             });
@@ -3619,6 +3565,12 @@
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
 
+    //TODO(b/257041702) delete
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        mShadeController.makeExpandedVisible(force);
+    }
+
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn(Runnable onDrawn) {
@@ -3899,8 +3851,7 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 if (BANNER_ACTION_SETUP.equals(action)) {
-                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */);
+                    mShadeController.animateCollapseShadeForced();
                     mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
@@ -3962,7 +3913,7 @@
                     action.run();
                 }).start();
 
-                return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+                return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
             }
 
             @Override
@@ -4057,8 +4008,7 @@
         mMainExecutor.execute(runnable);
     }
 
-    @Override
-    public void visibilityChanged(boolean visible) {
+    private void onShadeVisibilityChanged(boolean visible) {
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index aa0757e..000fe14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -240,8 +240,8 @@
                     && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
                             KeyguardUpdateMonitor.getCurrentUser())
                     && !needsFullscreenBouncer()
-                    && !mKeyguardUpdateMonitor.isFaceLockedOut()
-                    && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+                    && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                            BiometricSourceType.FACE)
                     && !mKeyguardBypassController.getBypassEnabled()) {
                 mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 44ad604..f9d316b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -469,6 +469,9 @@
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
             // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
             return;
+        } else if (mKeyguardStateController.isOccluded()
+                && !mDreamOverlayStateController.isOverlayActive()) {
+            return;
         } else if (needsFullscreenBouncer()) {
             if (mPrimaryBouncer != null) {
                 mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b6ae4a0..05bf860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -260,11 +260,11 @@
 
         if (showOverLockscreen) {
             mShadeController.addPostCollapseAction(runnable);
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         } else if (mKeyguardStateController.isShowing()
                 && mCentralSurfaces.isOccluded()) {
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
             runnable.run();
         }
@@ -406,7 +406,7 @@
 
     private void expandBubbleStack(NotificationEntry entry) {
         mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
-        mShadeController.collapsePanel();
+        mShadeController.collapseShade();
     }
 
     private void startNotificationIntent(
@@ -593,9 +593,9 @@
 
     private void collapseOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mMainThreadHandler.post(mShadeController::collapsePanel);
+            mMainThreadHandler.post(mShadeController::collapseShade);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a49850..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,7 +180,7 @@
                 }
             };
             mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
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 fcd1b8a..0662fb3 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
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -32,6 +35,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
 @Module
 abstract class StatusBarPipelineModule {
@@ -57,4 +61,15 @@
 
     @Binds
     abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+
+    @Module
+    companion object {
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        @WifiTableLog
+        fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("WifiTableLog", 100)
+        }
+    }
 }
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
similarity index 71%
copy from core/java/android/service/credentials/CreateCredentialResponse.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
index 73c9147..ac395a9 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package android.service.credentials;
+package com.android.systemui.statusbar.pipeline.dagger
 
-parcelable CreateCredentialResponse;
+import javax.inject.Qualifier
+
+/** Wifi logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index 062c3d1..8436b13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -17,12 +17,30 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.model
 
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.log.table.Diffable
 
 /** Provides information about the current wifi network. */
-sealed class WifiNetworkModel {
+sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+
     /** A model representing that we have no active wifi network. */
     object Inactive : WifiNetworkModel() {
         override fun toString() = "WifiNetwork.Inactive"
+
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal is Inactive) {
+                return
+            }
+            row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+
+            if (prevVal is CarrierMerged) {
+                // The only difference between CarrierMerged and Inactive is the type
+                return
+            }
+
+            // When changing from Active to Inactive, we need to log diffs to all the fields.
+            logDiffsFromActiveToNotActive(prevVal as Active, row)
+        }
     }
 
     /**
@@ -33,6 +51,21 @@
      */
     object CarrierMerged : WifiNetworkModel() {
         override fun toString() = "WifiNetwork.CarrierMerged"
+
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal is CarrierMerged) {
+                return
+            }
+            row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
+
+            if (prevVal is Inactive) {
+                // The only difference between CarrierMerged and Inactive is the type.
+                return
+            }
+
+            // When changing from Active to CarrierMerged, we need to log diffs to all the fields.
+            logDiffsFromActiveToNotActive(prevVal as Active, row)
+        }
     }
 
     /** Provides information about an active wifi network. */
@@ -76,6 +109,41 @@
             }
         }
 
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal !is Active) {
+                row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+            }
+
+            if (prevVal !is Active || prevVal.networkId != networkId) {
+                row.logChange(COL_NETWORK_ID, networkId)
+            }
+            if (prevVal !is Active || prevVal.isValidated != isValidated) {
+                row.logChange(COL_VALIDATED, isValidated)
+            }
+            if (prevVal !is Active || prevVal.level != level) {
+                row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT)
+            }
+            if (prevVal !is Active || prevVal.ssid != ssid) {
+                row.logChange(COL_SSID, ssid)
+            }
+
+            // TODO(b/238425913): The passpoint-related values are frequently never used, so it
+            //   would be great to not log them when they're not used.
+            if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
+                row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
+            }
+            if (prevVal !is Active ||
+                prevVal.isOnlineSignUpForPasspointAccessPoint !=
+                isOnlineSignUpForPasspointAccessPoint) {
+                row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
+            }
+            if (prevVal !is Active ||
+                prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
+                row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
+            }
+        }
+
+
         override fun toString(): String {
             // Only include the passpoint-related values in the string if we have them. (Most
             // networks won't have them so they'll be mostly clutter.)
@@ -101,4 +169,37 @@
             internal const val MAX_VALID_LEVEL = 4
         }
     }
+
+    internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) {
+        row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+        row.logChange(COL_VALIDATED, false)
+        row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+        row.logChange(COL_SSID, null)
+
+        if (prevActive.isPasspointAccessPoint) {
+            row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+        }
+        if (prevActive.isOnlineSignUpForPasspointAccessPoint) {
+            row.logChange(COL_ONLINE_SIGN_UP, false)
+        }
+        if (prevActive.passpointProviderFriendlyName != null) {
+            row.logChange(COL_PASSPOINT_NAME, null)
+        }
+    }
 }
+
+const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_INACTIVE = "Inactive"
+const val TYPE_ACTIVE = "Active"
+
+const val COL_NETWORK_TYPE = "type"
+const val COL_NETWORK_ID = "networkId"
+const val COL_VALIDATED = "isValidated"
+const val COL_LEVEL = "level"
+const val COL_SSID = "ssid"
+const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
+const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
+const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
+
+const val LEVEL_DEFAULT = -1
+const val NETWORK_ID_DEFAULT = -1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 93448c1d..a663536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -36,6 +36,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
@@ -82,6 +85,7 @@
     broadcastDispatcher: BroadcastDispatcher,
     connectivityManager: ConnectivityManager,
     logger: ConnectivityPipelineLogger,
+    @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     @Main mainExecutor: Executor,
     @Application scope: CoroutineScope,
     wifiManager: WifiManager?,
@@ -199,6 +203,12 @@
 
         awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
     }
+        .distinctUntilChanged()
+        .logDiffsForTable(
+            wifiTableLogBuffer,
+            columnPrefix = "wifiNetwork",
+            initialValue = WIFI_NETWORK_DEFAULT,
+        )
         // There will be multiple wifi icons in different places that will frequently
         // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
         // new subscribes will get the latest value immediately upon subscription. Otherwise, the
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index ad97ef4..5df4a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -29,6 +29,7 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -102,6 +103,15 @@
     }
 
     @Override
+    public Looper onProvideEngineLooper() {
+        // Receive messages on mWorker thread instead of SystemUI's main handler.
+        // All other wallpapers have their own process, and they can receive messages on their own
+        // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance
+        // of the image wallpaper could be negatively affected when SystemUI's main handler is busy.
+        return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper();
+    }
+
+    @Override
     public void onCreate() {
         super.onCreate();
         mWorker = new HandlerThread(TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4384d5..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -549,7 +549,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, e.getMessage());
         }
-        mShadeController.collapsePanel(true);
+        mShadeController.collapseShade(true);
         if (entry.getRow() != null) {
             entry.getRow().updateBubbleButton();
         }
@@ -597,7 +597,7 @@
         }
 
         if (shouldBubble) {
-            mShadeController.collapsePanel(true);
+            mShadeController.collapseShade(true);
             if (entry.getRow() != null) {
                 entry.getRow().updateBubbleButton();
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 8839662..afd582a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -63,7 +63,6 @@
     credentialAttempted = false,
     deviceInteractive = false,
     dreaming = false,
-    encryptedOrLockdown = false,
     fingerprintDisabled = false,
     fingerprintLockedOut = false,
     goingToSleep = false,
@@ -74,6 +73,7 @@
     primaryUser = false,
     shouldListenSfpsState = false,
     shouldListenForFingerprintAssistant = false,
+    strongerAuthRequired = false,
     switchingUser = false,
     udfps = false,
     userDoesNotHaveTrust = false
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 4d58b09..e39b9b5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -379,9 +379,9 @@
     }
 
     @Test
-    public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
+    public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
-        setNeedsStrongAuth(true);
+        setUnlockingWithFingerprintAllowed(false);
         reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -574,7 +574,7 @@
         attachView();
         setSideFpsHintEnabledFromResources(true);
         setFingerprintDetectionRunning(true);
-        setNeedsStrongAuth(false);
+        setUnlockingWithFingerprintAllowed(true);
     }
 
     private void attachView() {
@@ -593,9 +593,8 @@
                 enabled);
     }
 
-    private void setNeedsStrongAuth(boolean needed) {
-        when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed);
-        mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0);
+    private void setUnlockingWithFingerprintAllowed(boolean allowed) {
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed);
     }
 
     private void setupGetSecurityView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7231b34..63e1603 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 
@@ -281,7 +282,6 @@
                 componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
                 false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
                 false /* resetLockoutRequiresChallenge */));
-
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
@@ -594,30 +594,13 @@
     }
 
     @Test
-    public void testFingerprintDoesNotAuth_whenEncrypted() {
-        testFingerprintWhenStrongAuth(
-                STRONG_AUTH_REQUIRED_AFTER_BOOT);
-    }
-
-    @Test
-    public void testFingerprintDoesNotAuth_whenDpmLocked() {
-        testFingerprintWhenStrongAuth(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
-    }
-
-    @Test
-    public void testFingerprintDoesNotAuth_whenUserLockdown() {
-        testFingerprintWhenStrongAuth(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-    }
-
-    private void testFingerprintWhenStrongAuth(int strongAuth) {
+    public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
         // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
         // will trigger updateBiometricListeningState();
         clearInvocations(mFingerprintManager);
         mKeyguardUpdateMonitor.resetBiometricListeningState();
 
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
 
@@ -928,10 +911,6 @@
                 faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
         final boolean fpLocked =
                 fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
-                .thenReturn(fingerprintLockoutMode);
-        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
-                .thenReturn(faceLockoutMode);
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
@@ -940,7 +919,13 @@
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
         verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
                 anyInt());
+//        resetFaceManager();
+//        resetFingerprintManager();
 
+        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
+                .thenReturn(fingerprintLockoutMode);
+        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
+                .thenReturn(faceLockoutMode);
         final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
         final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
         mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -951,14 +936,22 @@
         mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
         mTestableLooper.processAllMessages();
 
-        verify(faceCancel, faceLocked ? times(1) : never()).cancel();
-        verify(fpCancel, fpLocked ? times(1) : never()).cancel();
-        verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
+        // THEN face and fingerprint listening are always cancelled immediately
+        verify(faceCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
                 eq(false), eq(BiometricSourceType.FACE));
-        verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
+        verify(fpCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
                 eq(false), eq(BiometricSourceType.FINGERPRINT));
+
+        // THEN locked out states are updated
         assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
         assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+
+        // Fingerprint should be restarted once its cancelled bc on lockout, the device
+        // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+        assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+                .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
     }
 
     @Test
@@ -1144,9 +1137,8 @@
         // GIVEN status bar state is on the keyguard
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
 
-        // WHEN user hasn't authenticated since last boot
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
-                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        // WHEN user hasn't authenticated since last boot, cannot unlock with FP
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1258,8 +1250,7 @@
         when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
 
         // WHEN device in lock down
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 0b528a5..eb8c823 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
-import javax.inject.Provider
+import com.android.systemui.util.mockito.any
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -46,15 +46,16 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
+import javax.inject.Provider
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -118,12 +119,13 @@
 
     @Test
     fun testFingerprintTrigger_KeyguardShowing_Ripple() {
-        // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
+        // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
         val fpsLocation = Point(5, 5)
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
 
         // WHEN fingerprint authenticated
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -140,11 +142,12 @@
 
     @Test
     fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
-        // GIVEN fp exists & user doesn't need strong auth
+        // GIVEN fp exists & unlocking with fp allowed
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
 
         // WHEN keyguard is NOT showing & fingerprint authenticated
         `when`(keyguardStateController.isShowing).thenReturn(false)
@@ -160,15 +163,16 @@
     }
 
     @Test
-    fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
+    fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
         // GIVEN fp exists & keyguard is showing
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
 
-        // WHEN user needs strong auth & fingerprint authenticated
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
+        // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
         captor.value.onBiometricAuthenticated(
@@ -182,13 +186,14 @@
 
     @Test
     fun testFaceTriggerBypassEnabled_Ripple() {
-        // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
+        // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed
         val faceLocation = Point(5, 5)
         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
         controller.onViewAttached()
 
         `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FACE)).thenReturn(true)
 
         // WHEN bypass is enabled & face authenticated
         `when`(bypassController.canBypass()).thenReturn(true)
@@ -275,6 +280,8 @@
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT)).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -295,6 +302,8 @@
         `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
         `when`(authController.isUdfpsFingerDown).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FACE))).thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FACE)
         assertTrue("reveal didn't start on keyguardFadingAway",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
new file mode 100644
index 0000000..60a0258
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class UdfpsEnrollViewTest extends SysuiTestCase {
+
+    private static String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3";
+    private static String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1";
+
+    @Test
+    public void fingerprintUdfpsEnroll_usesCorrectThemeCheckmarkFillColor() {
+        final Configuration config = mContext.getResources().getConfiguration();
+        final boolean isDarkThemeOn = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES;
+        final int currentColor = mContext.getColor(R.color.udfps_enroll_progress);
+
+        assertThat(currentColor).isEqualTo(Color.parseColor(isDarkThemeOn
+                ? ENROLL_PROGRESS_COLOR_DARK : ENROLL_PROGRESS_COLOR_LIGHT));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 98ff8d1..c677f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -31,6 +31,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.settingslib.applications.ServiceListing
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.dump.DumpManager
@@ -110,6 +111,12 @@
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
         mContext.setMockPackageManager(packageManager)
 
+        mContext.orCreateTestableResources
+                .addOverride(
+                        R.array.config_controlsPreferredPackages,
+                        arrayOf(componentName.packageName)
+                )
+
         // Return true by default, we'll test the false path
         `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
 
@@ -482,6 +489,35 @@
     }
 
     @Test
+    fun testPackageNotPreferred_nullPanel() {
+        mContext.orCreateTestableResources
+                .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
+
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
     fun testListingsNotModifiedByCallback() {
         // This test checks that if the list passed to the callback is modified, it has no effect
         // in the resulting services
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..cda7018
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -0,0 +1,193 @@
+/*
+ *  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.data.quickaffordance
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.utils.leaks.FakeFlashlightController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
+
+    @Mock private lateinit var context: Context
+    private lateinit var flashlightController: FakeFlashlightController
+    private lateinit var underTest : FlashlightQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        injectLeakCheckedDependency(FlashlightController::class.java)
+        MockitoAnnotations.initMocks(this)
+
+        flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController
+        underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
+    }
+
+    @Test
+    fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
+        //given
+        flashlightController.isEnabled = false
+        flashlightController.isAvailable = true
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+        //when
+        underTest.onTriggered(null)
+        val lastValue = values.last()
+
+        //then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertEquals(R.drawable.ic_flashlight_on,
+                ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
+        //given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = true
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+        //when
+        underTest.onTriggered(null)
+        val lastValue = values.last()
+
+        //then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertEquals(R.drawable.ic_flashlight_off,
+                ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
+        //given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+        //when
+        flashlightController.onFlashlightError()
+        val lastValue = values.last()
+
+        //then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertEquals(R.drawable.ic_flashlight_off,
+                ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight availability now off -- hidden`() = runTest {
+        //given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+        //when
+        flashlightController.onFlashlightAvailabilityChanged(false)
+        val lastValue = values.last()
+
+        //then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
+        //given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+        //when
+        flashlightController.onFlashlightAvailabilityChanged(true)
+        val lastValue = values.last()
+
+        //then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active)
+        assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
+        //given
+        flashlightController.isEnabled = false
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+        //when
+        flashlightController.onFlashlightAvailabilityChanged(true)
+        val lastValue = values.last()
+
+        //then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive)
+        assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight available -- picker state default`() = runTest {
+        //given
+        flashlightController.isAvailable = true
+
+        //when
+        val result = underTest.getPickerScreenState()
+
+        //then
+        assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+    }
+
+    @Test
+    fun `flashlight not available -- picker state unavailable`() = runTest {
+        //given
+        flashlightController.isAvailable = false
+
+        //when
+        val result = underTest.getPickerScreenState()
+
+        //then
+        assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
new file mode 100644
index 0000000..432764a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class TableChangeTest : SysuiTestCase() {
+
+    @Test
+    fun setString_isString() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set("fakeValue")
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("fakeValue")
+    }
+
+    @Test
+    fun setBoolean_isBoolean() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(true)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("true")
+    }
+
+    @Test
+    fun setInt_isInt() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(8900)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("8900")
+    }
+
+    @Test
+    fun setThenReset_isEmpty() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(8900)
+        underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name")
+
+        assertThat(underTest.hasData()).isFalse()
+        assertThat(underTest.getVal()).isEqualTo("null")
+    }
+
+    @Test
+    fun getName_hasPrefix() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+
+        assertThat(underTest.getName()).contains("fakePrefix")
+        assertThat(underTest.getName()).contains("fakeName")
+    }
+
+    @Test
+    fun getName_noPrefix() {
+        val underTest = TableChange(columnPrefix = "", columnName = "fakeName")
+
+        assertThat(underTest.getName()).contains("fakeName")
+    }
+
+    @Test
+    fun resetThenSet_hasNewValue() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original")
+        underTest.set("fakeValue")
+        underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated")
+        underTest.set(8900)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getName()).contains("updated")
+        assertThat(underTest.getName()).doesNotContain("prefix")
+        assertThat(underTest.getName()).doesNotContain("original")
+        assertThat(underTest.getVal()).isEqualTo("8900")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
new file mode 100644
index 0000000..688c66a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TableLogBufferTest : SysuiTestCase() {
+    private lateinit var underTest: TableLogBuffer
+
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var outputWriter: StringWriter
+
+    @Before
+    fun setup() {
+        systemClock = FakeSystemClock()
+        outputWriter = StringWriter()
+
+        underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun maxSizeZero_throwsException() {
+        TableLogBuffer(maxSize = 0, "name", systemClock)
+    }
+
+    @Test
+    fun dumpChanges_strChange_logsFromNext() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("stringValChange", "prevStringVal")
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("stringValChange", "newStringVal")
+                }
+            }
+
+        underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("prefix")
+        assertThat(dumpedString).contains("stringValChange")
+        assertThat(dumpedString).contains("newStringVal")
+        assertThat(dumpedString).doesNotContain("prevStringVal")
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+    }
+
+    @Test
+    fun dumpChanges_boolChange_logsFromNext() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", false)
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", true)
+                }
+            }
+
+        underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("prefix")
+        assertThat(dumpedString).contains("booleanValChange")
+        assertThat(dumpedString).contains("true")
+        assertThat(dumpedString).doesNotContain("false")
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+    }
+
+    @Test
+    fun dumpChanges_intChange_logsFromNext() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("intValChange", 12345)
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("intValChange", 67890)
+                }
+            }
+
+        underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("prefix")
+        assertThat(dumpedString).contains("intValChange")
+        assertThat(dumpedString).contains("67890")
+        assertThat(dumpedString).doesNotContain("12345")
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+    }
+
+    @Test
+    fun dumpChanges_noPrefix() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", false)
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", true)
+                }
+            }
+
+        // WHEN there's a blank prefix
+        underTest.logDiffs("", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        // THEN the dump still works
+        assertThat(dumpedString).contains("booleanValChange")
+        assertThat(dumpedString).contains("true")
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+    }
+
+    @Test
+    fun dumpChanges_multipleChangesForSameColumn_logs() {
+        lateinit var valToDump: String
+
+        val diffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("valChange", valToDump)
+                }
+            }
+
+        systemClock.setCurrentTimeMillis(12000L)
+        valToDump = "stateValue12"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        systemClock.setCurrentTimeMillis(20000L)
+        valToDump = "stateValue20"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        systemClock.setCurrentTimeMillis(40000L)
+        valToDump = "stateValue40"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        systemClock.setCurrentTimeMillis(45000L)
+        valToDump = "stateValue45"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("valChange")
+        assertThat(dumpedString).contains("stateValue12")
+        assertThat(dumpedString).contains("stateValue20")
+        assertThat(dumpedString).contains("stateValue40")
+        assertThat(dumpedString).contains("stateValue45")
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L))
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L))
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L))
+        assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L))
+    }
+
+    @Test
+    fun dumpChanges_multipleChangesAtOnce_logs() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("status", "in progress")
+                    row.logChange("connected", false)
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("status")
+        assertThat(dumpedString).contains("in progress")
+        assertThat(dumpedString).contains("connected")
+        assertThat(dumpedString).contains("false")
+    }
+
+    @Test
+    fun dumpChanges_rotatesIfBufferIsFull() {
+        lateinit var valToDump: String
+
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("status", valToDump)
+                }
+            }
+
+        for (i in 0 until MAX_SIZE + 3) {
+            valToDump = "testString[$i]"
+            underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+        }
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).doesNotContain("testString[0]")
+        assertThat(dumpedString).doesNotContain("testString[1]")
+        assertThat(dumpedString).doesNotContain("testString[2]")
+        assertThat(dumpedString).contains("testString[3]")
+        assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
+    }
+
+    private fun dumpChanges(): String {
+        underTest.dumpChanges(PrintWriter(outputWriter))
+        return outputWriter.toString()
+    }
+
+    private abstract class TestDiffable : Diffable<TestDiffable> {
+        override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {}
+    }
+}
+
+private const val NAME = "TestTableBuffer"
+private const val MAX_SIZE = 10
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index e1007fa..858d0e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -35,6 +35,8 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -50,10 +52,12 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.VariableDateView
 import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -104,7 +108,7 @@
     @Mock
     private lateinit var featureFlags: FeatureFlags
     @Mock
-    private lateinit var clock: TextView
+    private lateinit var clock: Clock
     @Mock
     private lateinit var date: VariableDateView
     @Mock
@@ -138,6 +142,7 @@
     private lateinit var qsConstraints: ConstraintSet
     @Mock
     private lateinit var largeScreenConstraints: ConstraintSet
+    @Mock private lateinit var demoModeController: DemoModeController
 
     @JvmField @Rule
     val mockitoRule = MockitoJUnit.rule()
@@ -146,10 +151,12 @@
     private lateinit var controller: LargeScreenShadeHeaderController
     private lateinit var carrierIconSlots: List<String>
     private val configurationController = FakeConfigurationController()
+    private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
 
     @Before
     fun setUp() {
-        whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+        demoModeControllerCapture = argumentCaptor<DemoMode>()
+        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
 
         whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
@@ -195,7 +202,8 @@
             dumpManager,
             featureFlags,
             qsCarrierGroupControllerBuilder,
-            combinedShadeHeadersConstraintManager
+            combinedShadeHeadersConstraintManager,
+            demoModeController
         )
         whenever(view.isAttachedToWindow).thenReturn(true)
         controller.init()
@@ -617,6 +625,21 @@
     }
 
     @Test
+    fun demoMode_attachDemoMode() {
+        verify(demoModeController).addCallback(capture(demoModeControllerCapture))
+        demoModeControllerCapture.value.onDemoModeStarted()
+        verify(clock).onDemoModeStarted()
+    }
+
+    @Test
+    fun demoMode_detachDemoMode() {
+        controller.simulateViewDetached()
+        verify(demoModeController).removeCallback(capture(demoModeControllerCapture))
+        demoModeControllerCapture.value.onDemoModeFinished()
+        verify(clock).onDemoModeFinished()
+    }
+
+    @Test
     fun animateOutOnStartCustomizing() {
         val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
         val duration = 1000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 90ae693..b4c8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -13,6 +13,8 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -22,9 +24,12 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -52,7 +57,7 @@
     @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
     @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
     @Mock private lateinit var featureFlags: FeatureFlags
-    @Mock private lateinit var clock: TextView
+    @Mock private lateinit var clock: Clock
     @Mock private lateinit var date: TextView
     @Mock private lateinit var carrierGroup: QSCarrierGroup
     @Mock private lateinit var batteryMeterView: BatteryMeterView
@@ -66,6 +71,7 @@
         CombinedShadeHeadersConstraintManager
 
     @Mock private lateinit var mockedContext: Context
+    @Mock private lateinit var demoModeController: DemoModeController
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
     var viewVisibility = View.GONE
@@ -76,7 +82,7 @@
 
     @Before
     fun setup() {
-        whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
         whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
@@ -111,8 +117,9 @@
                 dumpManager,
                 featureFlags,
                 qsCarrierGroupControllerBuilder,
-                combinedShadeHeadersConstraintManager
-        )
+                combinedShadeHeadersConstraintManager,
+                demoModeController
+                )
         whenever(view.isAttachedToWindow).thenReturn(true)
         mLargeScreenShadeHeaderController.init()
         carrierIconSlots = listOf(
@@ -230,4 +237,21 @@
         verify(animator).setInterpolator(Interpolators.ALPHA_IN)
         verify(animator).start()
     }
+
+    @Test
+    fun demoMode_attachDemoMode() {
+        val cb = argumentCaptor<DemoMode>()
+        verify(demoModeController).addCallback(capture(cb))
+        cb.value.onDemoModeStarted()
+        verify(clock).onDemoModeStarted()
+    }
+
+    @Test
+    fun demoMode_detachDemoMode() {
+        mLargeScreenShadeHeaderController.simulateViewDetached()
+        val cb = argumentCaptor<DemoMode>()
+        verify(demoModeController).removeCallback(capture(cb))
+        cb.value.onDemoModeFinished()
+        verify(clock).onDemoModeFinished()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index e1346ea..0000c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -118,13 +118,6 @@
     }
 
     @Test
-    public void testCollapsePanels() {
-        mCommandQueue.animateCollapsePanels();
-        waitForIdleSync();
-        verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
-    }
-
-    @Test
     public void testExpandSettings() {
         String panel = "some_panel";
         mCommandQueue.animateExpandSettingsPanel(panel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d5bfe1f..c17c5b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -136,7 +136,7 @@
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
 
         // Trying to open it does nothing.
         mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -154,7 +154,7 @@
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController, never()).animateCollapsePanels();
+        verify(mShadeController, never()).animateCollapseShade();
 
         // Can now be opened.
         mSbcqCallbacks.animateExpandNotificationsPanel();
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 013e727..ed84e42 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
@@ -392,10 +392,21 @@
             return null;
         }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
 
-        mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
-                mStatusBarStateController, mNotificationShadeWindowController,
-                mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+        mShadeController = spy(new ShadeControllerImpl(
+                mCommandQueue,
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mStatusBarKeyguardViewManager,
+                mStatusBarWindowController,
+                mNotificationShadeWindowController,
+                mContext.getSystemService(WindowManager.class),
+                () -> mAssistManager,
+                () -> mNotificationGutsManager
+        ));
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
+        mShadeController.setNotificationPresenter(mNotificationPresenter);
 
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
@@ -492,6 +503,7 @@
                 return mViewRootImpl;
             }
         };
+        mCentralSurfaces.initShadeVisibilityListener();
         when(mViewRootImpl.getOnBackInvokedDispatcher())
                 .thenReturn(mOnBackInvokedDispatcher);
         when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -807,7 +819,7 @@
 
         when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
         mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
@@ -1030,7 +1042,7 @@
     }
 
     @Test
-    public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+    public void collapseShade_callsanimateCollapseShade_whenExpanded() {
         // GIVEN the shade is expanded
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1038,12 +1050,12 @@
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+    public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
         // GIVEN the shade is collapsed
         mCentralSurfaces.onShadeExpansionFullyChanged(false);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1051,12 +1063,12 @@
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is NOT called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is NOT called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+    public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
         // GIVEN the shade is expanded & flag enabled
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1065,12 +1077,12 @@
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+    public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
         // GIVEN the shade is expanded & flag enabled
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1079,8 +1091,8 @@
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index d3b5418..df7ee43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -39,6 +39,7 @@
 
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -398,6 +399,8 @@
 
     @Test
     public void testShow_delaysIfFaceAuthIsRunning() {
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+                .thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
         mBouncer.show(true /* reset */);
 
@@ -410,9 +413,10 @@
     }
 
     @Test
-    public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() {
+    public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+                .thenReturn(false);
         mBouncer.show(true /* reset */);
 
         verify(mHandler, never()).postDelayed(any(), anyLong());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index bf5186b..e467d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,6 +307,17 @@
     }
 
     @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
         // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
         // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ce54d78..cae414a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -263,7 +263,7 @@
         while (!runnables.isEmpty()) runnables.remove(0).run();
 
         // Then
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
                 eq(false) /* animate */, any(), any());
@@ -296,7 +296,7 @@
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
         // This is called regardless, and simply short circuits when there is nothing to do.
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -329,7 +329,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -357,7 +357,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 3d29d2b..30fd308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -18,8 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
 @SmallTest
@@ -48,6 +50,125 @@
         WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
     }
 
+    // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
+
+    @Test
+    fun logDiffs_inactiveToActive_logsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+    }
+    @Test
+    fun logDiffs_activeToInactive_resetsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+    }
+
+    @Test
+    fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+    }
+    @Test
+    fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+    }
+
+    @Test
+    fun logDiffs_activeChangesLevel_onlyLevelLogged() {
+        val logger = TestLogger()
+        val prevActiveNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+        val newActiveNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 2,
+                ssid = "Test SSID"
+            )
+
+        newActiveNetwork.logDiffs(prevActiveNetwork, logger)
+
+        assertThat(logger.changes).isEqualTo(listOf(Pair(COL_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()))
+        }
+    }
+
     companion object {
         private const val NETWORK_ID = 2
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index a64a4bd..800f3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
@@ -69,6 +70,7 @@
 
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
     private lateinit var executor: Executor
@@ -804,6 +806,7 @@
             broadcastDispatcher,
             connectivityManager,
             logger,
+            tableLogger,
             executor,
             scope,
             wifiManagerToUse,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f6fd2cb..f68baf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -16,32 +16,71 @@
 
 import android.testing.LeakCheck;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
         implements FlashlightController {
+
+    private final List<FlashlightListener> callbacks = new ArrayList<>();
+
+    @VisibleForTesting
+    public boolean isAvailable;
+    @VisibleForTesting
+    public boolean isEnabled;
+    @VisibleForTesting
+    public boolean hasFlashlight;
+
     public FakeFlashlightController(LeakCheck test) {
         super(test, "flashlight");
     }
 
+    @VisibleForTesting
+    public void onFlashlightAvailabilityChanged(boolean newValue) {
+        callbacks.forEach(
+                flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue)
+        );
+    }
+
+    @VisibleForTesting
+    public void onFlashlightError() {
+        callbacks.forEach(FlashlightListener::onFlashlightError);
+    }
+
     @Override
     public boolean hasFlashlight() {
-        return false;
+        return hasFlashlight;
     }
 
     @Override
     public void setFlashlight(boolean newState) {
-
+        callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
     }
 
     @Override
     public boolean isAvailable() {
-        return false;
+        return isAvailable;
     }
 
     @Override
     public boolean isEnabled() {
-        return false;
+        return isEnabled;
+    }
+
+    @Override
+    public void addCallback(FlashlightListener listener) {
+        super.addCallback(listener);
+        callbacks.add(listener);
+    }
+
+    @Override
+    public void removeCallback(FlashlightListener listener) {
+        super.removeCallback(listener);
+        callbacks.remove(listener);
     }
 }
diff --git a/services/OWNERS b/services/OWNERS
index 495c0737..eace906 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -3,7 +3,7 @@
 # art-team@ manages the system server profile
 per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
 
-per-file java/com/android/server/* = toddke@google.com,patb@google.com
+per-file java/com/android/server/* = patb@google.com #{LAST_RESORT_SUGGESTION}
 per-file tests/servicestests/src/com/android/server/systemconfig/* = patb@google.com
 
 per-file proguard.flags = jdduke@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 87d1668..630cd350 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -860,7 +860,8 @@
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
                 return IntPair.of(
-                        getClientStateLocked(userState),
+                        combineUserStateAndProxyState(getClientStateLocked(userState),
+                                mProxyManager.getStateLocked()),
                         client.mLastSentRelevantEventTypes);
             } else {
                 userState.mUserClients.register(callback, client);
@@ -872,7 +873,9 @@
                             + " and userId:" + mCurrentUserId);
                 }
                 return IntPair.of(
-                        (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+                        (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
+                                getClientStateLocked(userState), mProxyManager.getStateLocked())
+                                : 0,
                         client.mLastSentRelevantEventTypes);
             }
         }
@@ -1003,6 +1006,7 @@
         notifyAccessibilityServicesDelayedLocked(event, false);
         notifyAccessibilityServicesDelayedLocked(event, true);
         mUiAutomationManager.sendAccessibilityEventLocked(event);
+        mProxyManager.sendAccessibilityEvent(event);
     }
 
     private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1143,9 +1147,9 @@
             }
             List<AccessibilityServiceConnection> services =
                     getUserStateLocked(resolvedUserId).mBoundServices;
-            int numServices = services.size();
+            int numServices = services.size() + mProxyManager.getNumProxys();
             interfacesToInterrupt = new ArrayList<>(numServices);
-            for (int i = 0; i < numServices; i++) {
+            for (int i = 0; i < services.size(); i++) {
                 AccessibilityServiceConnection service = services.get(i);
                 IBinder a11yServiceBinder = service.mService;
                 IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
@@ -1153,6 +1157,7 @@
                     interfacesToInterrupt.add(a11yServiceInterface);
                 }
             }
+            mProxyManager.addServiceInterfaces(interfacesToInterrupt);
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
@@ -1941,6 +1946,7 @@
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
+        relevantEventTypes |= mProxyManager.getRelevantEventTypes();
         return relevantEventTypes;
     }
 
@@ -2178,21 +2184,25 @@
         updateAccessibilityEnabledSettingLocked(userState);
     }
 
-    void scheduleUpdateClientsIfNeeded(AccessibilityUserState userState) {
-        synchronized (mLock) {
-            scheduleUpdateClientsIfNeededLocked(userState);
-        }
+    private int combineUserStateAndProxyState(int userState, int proxyState) {
+        return userState | proxyState;
     }
 
     void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
         final int clientState = getClientStateLocked(userState);
-        if (userState.getLastSentClientStateLocked() != clientState
+        final int proxyState = mProxyManager.getStateLocked();
+        if ((userState.getLastSentClientStateLocked() != clientState
+                || mProxyManager.getLastSentStateLocked() != proxyState)
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
-                        || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
+                || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
             userState.setLastSentClientStateLocked(clientState);
+            mProxyManager.setLastStateLocked(proxyState);
+            // Send both the user and proxy state to the app for now.
+            // TODO(b/250929565): Send proxy state to proxy clients
             mMainHandler.sendMessage(obtainMessage(
                     AccessibilityManagerService::sendStateToAllClients,
-                    this, clientState, userState.mUserId));
+                    this, combineUserStateAndProxyState(clientState, proxyState),
+                    userState.mUserId));
         }
     }
 
@@ -2434,7 +2444,8 @@
         // binding we do an update pass after each bind event, so we run this
         // code and register the callback if needed.
 
-        boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked();
+        boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked()
+                || mProxyManager.canRetrieveInteractiveWindowsLocked();
         List<AccessibilityServiceConnection> boundServices = userState.mBoundServices;
         final int boundServiceCount = boundServices.size();
         for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
@@ -3657,7 +3668,9 @@
                     + "proxy-ed");
         }
 
-        mProxyManager.registerProxy(client, displayId);
+        mProxyManager.registerProxy(client, displayId, mContext,
+                sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
+                mWindowManagerService, mA11yWindowManager);
         return true;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index e5e1d02..ce269f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -696,7 +696,7 @@
         final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo();
 
         if (resolveInfo == null) {
-            // For InteractionBridge and UiAutomation
+            // For InteractionBridge, UiAutomation, and Proxy.
             return true;
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 247f320..d7f9c12 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -16,8 +16,12 @@
 
 package com.android.server.accessibility;
 
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
+import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.content.ComponentName;
@@ -31,6 +35,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteCallback;
+import android.os.RemoteException;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityDisplayProxy;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -53,10 +58,6 @@
  * TODO(241429275): Initialize this when a proxy is registered.
  */
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
-    // Names used to populate ComponentName and ResolveInfo
-    private static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
-    private static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
-
     private int mDisplayId;
     private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
 
@@ -76,6 +77,16 @@
     }
 
     /**
+     * Called when the proxy is registered.
+     */
+    void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
+            throws RemoteException {
+        mServiceInterface = serviceInterface;
+        mService = serviceInterface.asBinder();
+        mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+    }
+
+    /**
      * Keeps mAccessibilityServiceInfo in sync with the proxy's list of AccessibilityServiceInfos.
      *
      * <p>This also sets the properties that are assumed to be populated by installed packages.
@@ -89,7 +100,7 @@
             synchronized (mLock) {
                 mInstalledAndEnabledServices = infos;
                 final AccessibilityServiceInfo proxyInfo = mAccessibilityServiceInfo;
-                // Reset values.
+                // Reset values. mAccessibilityServiceInfo is not completely reset since it is final
                 proxyInfo.flags = 0;
                 proxyInfo.eventTypes = 0;
                 proxyInfo.notificationTimeout = 0;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index a2ce610..2184878 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,9 +14,21 @@
  * limitations under the License.
  */
 package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 
-import java.util.HashSet;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.List;
 
 /**
  * Manages proxy connections.
@@ -27,32 +39,185 @@
  * TODO(241117292): Remove or cut down during simultaneous user refactoring.
  */
 public class ProxyManager {
+    // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
+    // the infos of connection.setInstalledAndEnabledServices
+    static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
+    static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
+
     private final Object mLock;
-    private final HashSet<Integer> mDisplayIds = new HashSet<>();
+
+    // Used to determine if we should notify AccessibilityManager clients of updates.
+    // TODO(254545943): Separate this so each display id has its own state. Currently there is no
+    // way to identify from AccessibilityManager which proxy state should be returned.
+    private int mLastState = -1;
+
+    private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+            new SparseArray<>();
 
     ProxyManager(Object lock) {
         mLock = lock;
     }
 
     /**
-     * TODO: Create the proxy service connection.
+     * Creates the service connection.
      */
-    public void registerProxy(IAccessibilityServiceClient client, int displayId) {
-        mDisplayIds.add(displayId);
+    public void registerProxy(IAccessibilityServiceClient client, int displayId,
+            Context context,
+            int id, Handler mainHandler,
+            AccessibilitySecurityPolicy securityPolicy,
+            AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
+            AccessibilityTrace trace,
+            WindowManagerInternal windowManagerInternal,
+            AccessibilityWindowManager awm) throws RemoteException {
+
+        // Set a default AccessibilityServiceInfo that is used before the proxy's info is
+        // populated. A proxy has the touch exploration and window capabilities.
+        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+        info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+                | AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
+        final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + displayId;
+        info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
+                componentClassDisplayName));
+        ProxyAccessibilityServiceConnection connection =
+                new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
+                        id, mainHandler, mLock, securityPolicy, systemSupport, trace,
+                        windowManagerInternal,
+                        awm, displayId);
+
+        mProxyA11yServiceConnections.put(displayId, connection);
+
+        // If the client dies, make sure to remove the connection.
+        IBinder.DeathRecipient deathRecipient =
+                new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        client.asBinder().unlinkToDeath(this, 0);
+                        clearConnection(displayId);
+                    }
+                };
+        client.asBinder().linkToDeath(deathRecipient, 0);
+        // Notify apps that the service state has changed.
+        // A11yManager#A11yServicesStateChangeListener
+        connection.mSystemSupport.onClientChangeLocked(true);
+
+        connection.initializeServiceInterface(client);
     }
 
     /**
-     * TODO: Unregister the proxy service connection based on display id.
+     * Unregister the proxy based on display id.
      */
     public boolean unregisterProxy(int displayId) {
-        mDisplayIds.remove(displayId);
-        return true;
+        return clearConnection(displayId);
+    }
+
+    private boolean clearConnection(int displayId) {
+        if (mProxyA11yServiceConnections.contains(displayId)) {
+            mProxyA11yServiceConnections.remove(displayId);
+            return true;
+        }
+        return false;
     }
 
     /**
      * Checks if a display id is being proxy-ed.
      */
     public boolean isProxyed(int displayId) {
-        return mDisplayIds.contains(displayId);
+        return mProxyA11yServiceConnections.contains(displayId);
     }
-}
+
+    /**
+     * Sends AccessibilityEvents to all proxies.
+     * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
+     * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
+     */
+    public void sendAccessibilityEvent(AccessibilityEvent event) {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            proxy.notifyAccessibilityEvent(event);
+        }
+    }
+
+    /**
+     * Returns {@code true} if any proxy can retrieve windows.
+     * TODO(b/250929565): Retrieve per connection/user state.
+     */
+    public boolean canRetrieveInteractiveWindowsLocked() {
+        boolean observingWindows = false;
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy.mRetrieveInteractiveWindows) {
+                observingWindows = true;
+                break;
+            }
+        }
+        return observingWindows;
+    }
+
+    /**
+     * If there is at least one proxy, accessibility is enabled.
+     */
+    public int getStateLocked() {
+        int clientState = 0;
+        final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
+        if (a11yEnabled) {
+            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+        }
+        return clientState;
+        // TODO(b/254545943): When A11yManager is separated, include support for other properties
+        // like isTouchExplorationEnabled.
+    }
+
+    /**
+     * Gets the last state.
+     */
+    public int getLastSentStateLocked() {
+        return mLastState;
+    }
+
+    /**
+     * Sets the last state.
+     */
+    public void setLastStateLocked(int proxyState) {
+        mLastState = proxyState;
+    }
+
+    /**
+     * Returns the relevant event types of every proxy.
+     * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+     */
+    public int getRelevantEventTypes() {
+        int relevantEventTypes = 0;
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            relevantEventTypes |= proxy.getRelevantEventTypes();
+        }
+        return relevantEventTypes;
+    }
+
+    /**
+     * Gets the number of current proxy connections.
+     * @return
+     */
+    public int getNumProxys() {
+        return mProxyA11yServiceConnections.size();
+    }
+
+    /**
+     * Adds the service interfaces to a list.
+     * @param interfaces
+     */
+    public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            final IBinder proxyBinder = proxy.mService;
+            final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+            if ((proxyBinder != null) && (proxyInterface != null)) {
+                interfaces.add(proxyInterface);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/api/current.txt b/services/api/current.txt
index 42ae10e..834ed2f 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -76,6 +76,7 @@
     method @Nullable public String getSdkLibraryName();
     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 public boolean isDebuggable();
     method public boolean isIsolatedSplitLoading();
@@ -99,6 +100,7 @@
     method public int getAppId();
     method @NonNull public String getPackageName();
     method @Nullable public String getPrimaryCpuAbi();
+    method @Nullable public String getSeInfo();
     method @Nullable public String getSecondaryCpuAbi();
     method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 3cfae60..8baae53a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -35,6 +35,7 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal;
+import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
@@ -257,6 +258,9 @@
     private boolean mIsProviderInfoPersisted;
     private boolean mIsCombinedBroadcastEnabled;
 
+    // Mark widget lifecycle broadcasts as 'interactive'
+    private Bundle mInteractiveBroadcast;
+
     AppWidgetServiceImpl(Context context) {
         mContext = context;
     }
@@ -286,6 +290,11 @@
             Slog.d(TAG, "App widget provider info will not be persisted on this device");
         }
 
+        BroadcastOptions opts = BroadcastOptions.makeBasic();
+        opts.setBackgroundActivityStartsAllowed(false);
+        opts.setInteractive(true);
+        mInteractiveBroadcast = opts.toBundle();
+
         computeMaximumWidgetBitmapMemory();
         registerBroadcastReceiver();
         registerOnCrossProfileProvidersChangedListener();
@@ -2379,33 +2388,40 @@
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
         intent.setComponent(p.id.componentName);
-        sendBroadcastAsUser(intent, p.id.getProfile());
+        // Placing a widget is something users expect to be UX-responsive, so mark this
+        // broadcast as interactive
+        sendBroadcastAsUser(intent, p.id.getProfile(), true);
     }
 
     private void sendEnableIntentLocked(Provider p) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
         intent.setComponent(p.id.componentName);
-        sendBroadcastAsUser(intent, p.id.getProfile());
+        // Enabling the widget is something users expect to be UX-responsive, so mark this
+        // broadcast as interactive
+        sendBroadcastAsUser(intent, p.id.getProfile(), true);
     }
 
     private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
         intent.setComponent(provider.id.componentName);
-        sendBroadcastAsUser(intent, provider.id.getProfile());
+        // Periodic background widget update heartbeats are not an interactive use case
+        sendBroadcastAsUser(intent, provider.id.getProfile(), false);
     }
 
     private void sendDeletedIntentLocked(Widget widget) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
         intent.setComponent(widget.provider.id.componentName);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
-        sendBroadcastAsUser(intent, widget.provider.id.getProfile());
+        // Cleanup after deletion isn't an interactive UX case
+        sendBroadcastAsUser(intent, widget.provider.id.getProfile(), false);
     }
 
     private void sendDisabledIntentLocked(Provider provider) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
         intent.setComponent(provider.id.componentName);
-        sendBroadcastAsUser(intent, provider.id.getProfile());
+        // Cleanup after disable isn't an interactive UX case
+        sendBroadcastAsUser(intent, provider.id.getProfile(), false);
     }
 
     public void sendOptionsChangedIntentLocked(Widget widget) {
@@ -2413,7 +2429,9 @@
         intent.setComponent(widget.provider.id.componentName);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options);
-        sendBroadcastAsUser(intent, widget.provider.id.getProfile());
+        // The user's changed the options, so seeing them take effect promptly is
+        // an interactive UX expectation
+        sendBroadcastAsUser(intent, widget.provider.id.getProfile(), true);
     }
 
     @GuardedBy("mLock")
@@ -3666,10 +3684,17 @@
         return null;
     }
 
-    private void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+    /**
+     * Sends a widget lifecycle broadcast within the specified user.  If {@code isInteractive}
+     * is specified as {@code true}, the broadcast dispatch mechanism will be told that it
+     * is related to a UX flow with user-visible expectations about timely dispatch.  This
+     * should only be used for broadcast flows that do have such expectations.
+     */
+    private void sendBroadcastAsUser(Intent intent, UserHandle userHandle, boolean isInteractive) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            mContext.sendBroadcastAsUser(intent, userHandle);
+            mContext.sendBroadcastAsUser(intent, userHandle, null,
+                    isInteractive ? mInteractiveBroadcast : null);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -5008,18 +5033,20 @@
 
         private void sendWidgetRestoreBroadcastLocked(String action, Provider provider,
                 Host host, int[] oldIds, int[] newIds, UserHandle userHandle) {
+            // Users expect restore to emplace widgets properly ASAP, so flag these as
+            // being interactive broadcast dispatches
             Intent intent = new Intent(action);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
             if (provider != null) {
                 intent.setComponent(provider.id.componentName);
-                sendBroadcastAsUser(intent, userHandle);
+                sendBroadcastAsUser(intent, userHandle, true);
             }
             if (host != null) {
                 intent.setComponent(null);
                 intent.setPackage(host.id.packageName);
                 intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.id.hostId);
-                sendBroadcastAsUser(intent, userHandle);
+                sendBroadcastAsUser(intent, userHandle, true);
             }
         }
 
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 0fe90b1..f5d6836 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -9,7 +9,7 @@
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
-import android.app.backup.BackupManager;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.IBackupCallback;
@@ -148,7 +148,7 @@
         try {
             return mBackupManagerService.bindToAgentSynchronous(targetApp,
                     ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
-                    BackupManager.OperationType.BACKUP);
+                    BackupAnnotations.BackupDestination.CLOUD);
         } catch (SecurityException e) {
             Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
                     + ". " + e);
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 4cf63b3..ce3e628 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -46,8 +46,8 @@
 import android.app.IBackupAgent;
 import android.app.PendingIntent;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManager;
@@ -405,7 +405,7 @@
     private long mAncestralToken = 0;
     private long mCurrentToken = 0;
     @Nullable private File mAncestralSerialNumberFile;
-    @OperationType private volatile long mAncestralOperationType;
+    @BackupDestination private volatile long mAncestralBackupDestination;
 
     private final ContentObserver mSetupObserver;
     private final BroadcastReceiver mRunInitReceiver;
@@ -550,7 +550,7 @@
         mActivityManager = ActivityManager.getService();
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId,
-                OperationType.BACKUP);
+                BackupDestination.CLOUD);
 
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -844,8 +844,8 @@
         mAncestralToken = ancestralToken;
     }
 
-    public void setAncestralOperationType(@OperationType int operationType) {
-        mAncestralOperationType = operationType;
+    public void setAncestralBackupDestination(@BackupDestination int backupDestination) {
+        mAncestralBackupDestination = backupDestination;
     }
 
     public long getCurrentToken() {
@@ -1619,14 +1619,14 @@
     /** Fires off a backup agent, blocking until it attaches or times out. */
     @Nullable
     public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
-            @OperationType int operationType) {
+            @BackupDestination int backupDestination) {
         IBackupAgent agent = null;
         synchronized (mAgentConnectLock) {
             mConnecting = true;
             mConnectedAgent = null;
             try {
                 if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
-                        operationType)) {
+                        backupDestination)) {
                     Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
 
                     // success; wait for the agent to arrive
@@ -1776,8 +1776,9 @@
     }
 
     private BackupEligibilityRules getEligibilityRulesForRestoreAtInstall(long restoreToken) {
-        if (mAncestralOperationType == OperationType.MIGRATION && restoreToken == mAncestralToken) {
-            return getEligibilityRulesForOperation(OperationType.MIGRATION);
+        if (mAncestralBackupDestination == BackupDestination.DEVICE_TRANSFER
+                && restoreToken == mAncestralToken) {
+            return getEligibilityRulesForOperation(BackupDestination.DEVICE_TRANSFER);
         } else {
             // If we're not using the ancestral data set, it means we're restoring from a backup
             // that happened on this device.
@@ -1856,14 +1857,14 @@
 
         final TransportConnection transportConnection;
         final String transportDirName;
-        int operationType;
+        int backupDestination;
         try {
             transportDirName =
                     mTransportManager.getTransportDirName(
                             mTransportManager.getCurrentTransportName());
             transportConnection =
                     mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()");
-            operationType = getOperationTypeFromTransport(transportConnection);
+            backupDestination = getBackupDestinationFromTransport(transportConnection);
         } catch (TransportNotRegisteredException | TransportNotAvailableException
                 | RemoteException e) {
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -1876,7 +1877,7 @@
         OnTaskFinishedListener listener =
                 caller -> mTransportManager.disposeOfTransportClient(transportConnection, caller);
         BackupEligibilityRules backupEligibilityRules = getEligibilityRulesForOperation(
-                operationType);
+                backupDestination);
 
         Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
         msg.obj = getRequestBackupParams(packages, observer, monitor, flags, backupEligibilityRules,
@@ -2373,7 +2374,7 @@
                             /* monitor */ null,
                             /* userInitiated */ false,
                             "BMS.beginFullBackup()",
-                            getEligibilityRulesForOperation(OperationType.BACKUP));
+                            getEligibilityRulesForOperation(BackupDestination.CLOUD));
                 } catch (IllegalStateException e) {
                     Slog.w(TAG, "Failed to start backup", e);
                     runBackup = false;
@@ -2835,7 +2836,7 @@
             Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup..."));
 
             BackupEligibilityRules eligibilityRules = getEligibilityRulesForOperation(
-                    OperationType.ADB_BACKUP);
+                    BackupDestination.ADB_BACKUP);
             AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
                     includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
                     pkgList, eligibilityRules);
@@ -2924,7 +2925,7 @@
                         /* monitor */ null,
                         /* userInitiated */ false,
                         "BMS.fullTransportBackup()",
-                        getEligibilityRulesForOperation(OperationType.BACKUP));
+                        getEligibilityRulesForOperation(BackupDestination.CLOUD));
                 // Acquiring wakelock for PerformFullTransportBackupTask before its start.
                 mWakelock.acquire();
                 (new Thread(task, "full-transport-master")).start();
@@ -3917,12 +3918,12 @@
             }
         }
 
-        int operationType;
+        int backupDestination;
         TransportConnection transportConnection = null;
         try {
             transportConnection = mTransportManager.getTransportClientOrThrow(
                     transport, /* caller */"BMS.beginRestoreSession");
-            operationType = getOperationTypeFromTransport(transportConnection);
+            backupDestination = getBackupDestinationFromTransport(transportConnection);
         } catch (TransportNotAvailableException | TransportNotRegisteredException
                 | RemoteException e) {
             Slog.w(TAG, "Failed to get operation type from transport: " + e);
@@ -3951,7 +3952,7 @@
                 return null;
             }
             mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport,
-                    getEligibilityRulesForOperation(operationType));
+                    getEligibilityRulesForOperation(backupDestination));
             mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
                     mAgentTimeoutParameters.getRestoreSessionTimeoutMillis());
         }
@@ -4037,14 +4038,14 @@
     }
 
     public BackupEligibilityRules getEligibilityRulesForOperation(
-            @OperationType int operationType) {
-        return getEligibilityRules(mPackageManager, mUserId, operationType);
+            @BackupDestination int backupDestination) {
+        return getEligibilityRules(mPackageManager, mUserId, backupDestination);
     }
 
     private static BackupEligibilityRules getEligibilityRules(PackageManager packageManager,
-            int userId, @OperationType int operationType) {
+            int userId, @BackupDestination int backupDestination) {
         return new BackupEligibilityRules(packageManager,
-                LocalServices.getService(PackageManagerInternal.class), userId, operationType);
+                LocalServices.getService(PackageManagerInternal.class), userId, backupDestination);
     }
 
     /** Prints service state for 'dumpsys backup'. */
@@ -4200,21 +4201,22 @@
     }
 
     @VisibleForTesting
-    @OperationType int getOperationTypeFromTransport(TransportConnection transportConnection)
+    @BackupDestination int getBackupDestinationFromTransport(
+            TransportConnection transportConnection)
             throws TransportNotAvailableException, RemoteException {
         if (!shouldUseNewBackupEligibilityRules()) {
             // Return the default to stick to the legacy behaviour.
-            return OperationType.BACKUP;
+            return BackupDestination.CLOUD;
         }
 
         final long oldCallingId = Binder.clearCallingIdentity();
         try {
             BackupTransportClient transport = transportConnection.connectOrThrow(
-                    /* caller */ "BMS.getOperationTypeFromTransport");
+                    /* caller */ "BMS.getBackupDestinationFromTransport");
             if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
-                return OperationType.MIGRATION;
+                return BackupDestination.DEVICE_TRANSFER;
             } else {
-                return OperationType.BACKUP;
+                return BackupDestination.CLOUD;
             }
         } finally {
             Binder.restoreCallingIdentity(oldCallingId);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 379ae52..65682f4 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -315,7 +315,7 @@
             mAgent =
                     backupManagerService.bindToAgentSynchronous(
                             mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
-                            mBackupEligibilityRules.getOperationType());
+                            mBackupEligibilityRules.getBackupDestination());
         }
         return mAgent != null;
     }
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 95cc289..3ff6ba7 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -20,7 +20,7 @@
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.RestoreSet;
 import android.os.Handler;
@@ -240,7 +240,7 @@
                                 /* userInitiated */ false,
                                 /* nonIncremental */ false,
                                 backupManagerService.getEligibilityRulesForOperation(
-                                        OperationType.BACKUP));
+                                        BackupDestination.CLOUD));
                     } catch (Exception e) {
                         // unable to ask the transport its dir name -- transient failure, since
                         // the above check succeeded.  Try again next time.
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index fd9c834..ca92b69 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -741,7 +741,7 @@
             agent =
                     mBackupManagerService.bindToAgentSynchronous(
                             packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL,
-                            mBackupEligibilityRules.getOperationType());
+                            mBackupEligibilityRules.getBackupDestination());
             if (agent == null) {
                 mReporter.onAgentError(packageName);
                 throw AgentException.transitory();
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 8b1d561..d3e4f13 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -16,8 +16,6 @@
 
 package com.android.server.backup.restore;
 
-import static android.app.backup.BackupManager.OperationType;
-
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
@@ -26,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
@@ -299,9 +298,9 @@
     private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) {
         // TODO(b/182986784): Remove device name comparison once a designated field for operation
         //  type is added to RestoreSet object.
-        int operationType = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
-                ? OperationType.MIGRATION : OperationType.BACKUP;
-        return mBackupManagerService.getEligibilityRulesForOperation(operationType);
+        int backupDestination = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
+                ? BackupDestination.DEVICE_TRANSFER : BackupDestination.CLOUD;
+        return mBackupManagerService.getEligibilityRulesForOperation(backupDestination);
     }
 
     public synchronized int restorePackage(String packageName, IRestoreObserver observer,
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index e78c8d1..b042c30 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -28,6 +28,7 @@
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManagerMonitor;
@@ -398,7 +399,7 @@
                                     FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
                                             ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                                             : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
-                                    mBackupEligibilityRules.getOperationType());
+                                    mBackupEligibilityRules.getBackupDestination());
                             mAgentPackage = pkg;
                         } catch (IOException | NameNotFoundException e) {
                             // fall through to error handling
@@ -707,7 +708,8 @@
     }
 
     private boolean isRestorableFile(FileMetadata info) {
-        if (mBackupEligibilityRules.getOperationType() == BackupManager.OperationType.MIGRATION) {
+        if (mBackupEligibilityRules.getBackupDestination()
+                == BackupAnnotations.BackupDestination.DEVICE_TRANSFER) {
             // Everything is eligible for device-to-device migration.
             return true;
         }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 22af19e..515a172 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -24,7 +24,7 @@
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION;
 
-import android.app.backup.BackupManager;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IFullBackupRestoreObserver;
 import android.content.pm.PackageManagerInternal;
 import android.os.ParcelFileDescriptor;
@@ -112,7 +112,7 @@
             BackupEligibilityRules eligibilityRules = new BackupEligibilityRules(
                     mBackupManagerService.getPackageManager(),
                     LocalServices.getService(PackageManagerInternal.class),
-                    mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP);
+                    mBackupManagerService.getUserId(), BackupDestination.ADB_BACKUP);
             FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
                     mOperationStorage, null, mObserver, null, null,
                     true, 0 /*unused*/, true, eligibilityRules);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 9f89339..18e28de 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -675,7 +675,7 @@
         mAgent = backupManagerService.bindToAgentSynchronous(
                 mCurrentPackage.applicationInfo,
                 ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
-                mBackupEligibilityRules.getOperationType());
+                mBackupEligibilityRules.getBackupDestination());
         if (mAgent == null) {
             Slog.w(TAG, "Can't find backup agent for " + packageName);
             mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -1160,8 +1160,8 @@
         if (mIsSystemRestore && mPmAgent != null) {
             backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
             backupManagerService.setAncestralToken(mToken);
-            backupManagerService.setAncestralOperationType(
-                    mBackupEligibilityRules.getOperationType());
+            backupManagerService.setAncestralBackupDestination(
+                    mBackupEligibilityRules.getBackupDestination());
             backupManagerService.writeRestoreTokens();
         }
 
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index d0300ff..7f0b56f 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -23,7 +23,7 @@
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.annotation.Nullable;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupTransport;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
@@ -61,7 +61,7 @@
     private final PackageManager mPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
     private final int mUserId;
-    @OperationType  private final int mOperationType;
+    @BackupDestination  private final int mBackupDestination;
 
     /**
      * When  this change is enabled, {@code adb backup}  is automatically turned on for apps
@@ -85,17 +85,17 @@
             PackageManagerInternal packageManagerInternal,
             int userId) {
         return new BackupEligibilityRules(packageManager, packageManagerInternal, userId,
-                OperationType.BACKUP);
+                BackupDestination.CLOUD);
     }
 
     public BackupEligibilityRules(PackageManager packageManager,
             PackageManagerInternal packageManagerInternal,
             int userId,
-            @OperationType int operationType) {
+            @BackupDestination int backupDestination) {
         mPackageManager = packageManager;
         mPackageManagerInternal = packageManagerInternal;
         mUserId = userId;
-        mOperationType = operationType;
+        mBackupDestination = backupDestination;
     }
 
     /**
@@ -111,7 +111,7 @@
      * </ol>
      *
      * However, the above eligibility rules are ignored for non-system apps in in case of
-     * device-to-device migration, see {@link OperationType}.
+     * device-to-device migration, see {@link BackupDestination}.
      */
     @VisibleForTesting
     public boolean appIsEligibleForBackup(ApplicationInfo app) {
@@ -152,22 +152,22 @@
     /**
     * Check if this app allows backup. Apps can opt out of backup by stating
     * android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
-    * apps during device-to-device migrations, see {@link OperationType}.
+    * apps during device-to-device migrations, see {@link BackupDestination}.
     *
     * @param app The app under check.
     * @return boolean indicating whether backup is allowed.
     */
     public boolean isAppBackupAllowed(ApplicationInfo app) {
         boolean allowBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
-        switch (mOperationType) {
-            case OperationType.MIGRATION:
+        switch (mBackupDestination) {
+            case BackupDestination.DEVICE_TRANSFER:
                 // Backup / restore of all non-system apps is force allowed during
                 // device-to-device migration.
                 boolean isSystemApp = (app.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
                 boolean ignoreAllowBackup = !isSystemApp && CompatChanges.isChangeEnabled(
                         IGNORE_ALLOW_BACKUP_IN_D2D, app.packageName, UserHandle.of(mUserId));
                 return ignoreAllowBackup || allowBackup;
-            case OperationType.ADB_BACKUP:
+            case BackupDestination.ADB_BACKUP:
                 String packageName = app.packageName;
                 if (packageName == null) {
                     Slog.w(TAG, "Invalid ApplicationInfo object");
@@ -207,10 +207,10 @@
                     // All other apps can use adb backup only when running in debuggable mode.
                     return isDebuggable;
                 }
-            case OperationType.BACKUP:
+            case BackupDestination.CLOUD:
                 return allowBackup;
             default:
-                Slog.w(TAG, "Unknown operation type:" + mOperationType);
+                Slog.w(TAG, "Unknown operation type:" + mBackupDestination);
                 return false;
         }
     }
@@ -398,7 +398,7 @@
         }
     }
 
-    public int getOperationType() {
-        return mOperationType;
+    public int getBackupDestination() {
+        return mBackupDestination;
     }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index ce7854d..2814196 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -130,6 +130,7 @@
     @Nullable
     private final @AssociationRequest.DeviceProfile String mDeviceProfile;
     @Nullable private final SecureWindowCallback mSecureWindowCallback;
+    @Nullable private final List<String> mDisplayCategories;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -168,7 +169,8 @@
             @NonNull PipBlockedCallback pipBlockedCallback,
             @NonNull ActivityBlockedCallback activityBlockedCallback,
             @NonNull SecureWindowCallback secureWindowCallback,
-            @AssociationRequest.DeviceProfile String deviceProfile) {
+            @AssociationRequest.DeviceProfile String deviceProfile,
+            @NonNull List<String> displayCategories) {
         super();
         mAllowedUsers = allowedUsers;
         mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
@@ -182,6 +184,7 @@
         mDeviceProfile = deviceProfile;
         mPipBlockedCallback = pipBlockedCallback;
         mSecureWindowCallback = secureWindowCallback;
+        mDisplayCategories = displayCategories;
     }
 
     /**
@@ -319,7 +322,7 @@
         if (mDeviceProfile == null) {
             return true;
         }
-       // TODO(b/234075973) : Remove this once proper API is ready.
+        // TODO(b/234075973) : Remove this once proper API is ready.
         switch (mDeviceProfile) {
             case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
                 return false;
@@ -350,6 +353,15 @@
         }
     }
 
+    private boolean activityMatchesDisplayCategory(ActivityInfo activityInfo) {
+        if (mDisplayCategories.isEmpty()) {
+            return activityInfo.targetDisplayCategory == null;
+        }
+        return activityInfo.targetDisplayCategory != null
+                    && mDisplayCategories.contains(activityInfo.targetDisplayCategory);
+
+    }
+
     private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
         if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
@@ -357,9 +369,17 @@
         }
         ComponentName activityComponent = activityInfo.getComponentName();
         if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent)) {
-            // The error dialog alerting users that streaming is blocked is always allowed.
+            // The error dialog alerting users that streaming is blocked is always allowed. Need to
+            // run before the clauses below to ensure error dialog always shows up.
             return true;
         }
+        if (!activityMatchesDisplayCategory(activityInfo)) {
+            Slog.d(TAG, String.format(
+                    "The activity's target display category: %s is not found on virtual display"
+                            + " with the following allowed display categories: %s",
+                    activityInfo.targetDisplayCategory, mDisplayCategories.toString()));
+            return false;
+        }
         final UserHandle activityUser =
                 UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
         if (!mAllowedUsers.contains(activityUser)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
new file mode 100644
index 0000000..ec7e993
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -0,0 +1,235 @@
+/*
+ * 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.companion.virtual;
+
+import android.annotation.NonNull;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
+public class SensorController {
+
+    private static final String TAG = "SensorController";
+
+    private final Object mLock;
+    private final int mVirtualDeviceId;
+    @GuardedBy("mLock")
+    private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+
+    private final SensorManagerInternal mSensorManagerInternal;
+
+    public SensorController(@NonNull Object lock, int virtualDeviceId) {
+        mLock = lock;
+        mVirtualDeviceId = virtualDeviceId;
+        mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+    }
+
+    void close() {
+        synchronized (mLock) {
+            final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
+                    mSensorDescriptors.entrySet().iterator();
+            if (iterator.hasNext()) {
+                final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
+                final IBinder token = entry.getKey();
+                final SensorDescriptor sensorDescriptor = entry.getValue();
+                iterator.remove();
+                closeSensorDescriptorLocked(token, sensorDescriptor);
+            }
+        }
+    }
+
+    void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
+        Objects.requireNonNull(deviceToken);
+        Objects.requireNonNull(config);
+        try {
+            createSensorInternal(deviceToken, config);
+        } catch (SensorCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual sensor '" + config.getName() + "'.", e);
+        }
+    }
+
+    private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+            throws SensorCreationException {
+        final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
+                (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
+                    IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
+                    if (callback != null) {
+                        try {
+                            callback.onStateChanged(
+                                    enabled, samplingPeriodMicros, batchReportLatencyMicros);
+                        } catch (RemoteException e) {
+                            throw new RuntimeException("Failed to call sensor callback.", e);
+                        }
+                    }
+                };
+
+        final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
+                config.getType(), config.getName(),
+                config.getVendor() == null ? "" : config.getVendor(),
+                runtimeSensorCallback);
+        if (handle <= 0) {
+            throw new SensorCreationException("Received an invalid virtual sensor handle.");
+        }
+
+        // The handle is valid from here, so ensure that all failures clean it up.
+        final BinderDeathRecipient binderDeathRecipient;
+        try {
+            binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+        } catch (RemoteException e) {
+            mSensorManagerInternal.removeRuntimeSensor(handle);
+            throw new SensorCreationException("Client died before sensor could be created.", e);
+        }
+
+        synchronized (mLock) {
+            SensorDescriptor sensorDescriptor = new SensorDescriptor(
+                    handle, config.getType(), config.getName(), binderDeathRecipient);
+            mSensorDescriptors.put(deviceToken, sensorDescriptor);
+        }
+    }
+
+    boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+        Objects.requireNonNull(token);
+        Objects.requireNonNull(event);
+        synchronized (mLock) {
+            final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+            if (sensorDescriptor == null) {
+                throw new IllegalArgumentException("Could not send sensor event for given token");
+            }
+            return mSensorManagerInternal.sendSensorEvent(
+                    sensorDescriptor.getHandle(), sensorDescriptor.getType(),
+                    event.getTimestampNanos(), event.getValues());
+        }
+    }
+
+    void unregisterSensor(@NonNull IBinder token) {
+        Objects.requireNonNull(token);
+        synchronized (mLock) {
+            final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
+            if (sensorDescriptor == null) {
+                throw new IllegalArgumentException("Could not unregister sensor for given token");
+            }
+            closeSensorDescriptorLocked(token, sensorDescriptor);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
+        token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
+        final int handle = sensorDescriptor.getHandle();
+        mSensorManagerInternal.removeRuntimeSensor(handle);
+    }
+
+
+    void dump(@NonNull PrintWriter fout) {
+        fout.println("    SensorController: ");
+        synchronized (mLock) {
+            fout.println("      Active descriptors: ");
+            for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
+                fout.println("        handle: " + sensorDescriptor.getHandle());
+                fout.println("          type: " + sensorDescriptor.getType());
+                fout.println("          name: " + sensorDescriptor.getName());
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
+        synchronized (mLock) {
+            mSensorDescriptors.put(deviceToken,
+                    new SensorDescriptor(handle, type, name, () -> {}));
+        }
+    }
+
+    @VisibleForTesting
+    Map<IBinder, SensorDescriptor> getSensorDescriptors() {
+        synchronized (mLock) {
+            return mSensorDescriptors;
+        }
+    }
+
+    @VisibleForTesting
+    static final class SensorDescriptor {
+
+        private final int mHandle;
+        private final IBinder.DeathRecipient mDeathRecipient;
+        private final int mType;
+        private final String mName;
+
+        SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
+            mHandle = handle;
+            mDeathRecipient = deathRecipient;
+            mType = type;
+            mName = name;
+        }
+        public int getHandle() {
+            return mHandle;
+        }
+        public int getType() {
+            return mType;
+        }
+        public String getName() {
+            return mName;
+        }
+        public IBinder.DeathRecipient getDeathRecipient() {
+            return mDeathRecipient;
+        }
+    }
+
+    private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+        private final IBinder mDeviceToken;
+
+        BinderDeathRecipient(IBinder deviceToken) {
+            mDeviceToken = deviceToken;
+        }
+
+        @Override
+        public void binderDied() {
+            // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
+            // quitting, which removes this death recipient. If this is invoked, the remote end
+            // died, or they disposed of the object without properly unregistering.
+            Slog.e(TAG, "Virtual sensor controller binder died");
+            unregisterSensor(mDeviceToken);
+        }
+    }
+
+    /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
+    private static class SensorCreationException extends Exception {
+        SensorCreationException(String message) {
+            super(message);
+        }
+        SensorCreationException(String message, Exception cause) {
+            super(message, cause);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 0def25d..828f302 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -38,6 +38,8 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -74,7 +76,9 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -97,6 +101,7 @@
     private final int mOwnerUid;
     private final int mDeviceId;
     private final InputController mInputController;
+    private final SensorController mSensorController;
     private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
     final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -159,6 +164,7 @@
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
+                /* sensorController= */ null,
                 listener,
                 pendingTrampolineCallback,
                 activityListener,
@@ -174,6 +180,7 @@
             int ownerUid,
             int deviceId,
             InputController inputController,
+            SensorController sensorController,
             OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
@@ -197,6 +204,11 @@
         } else {
             mInputController = inputController;
         }
+        if (sensorController == null) {
+            mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+        } else {
+            mSensorController = sensorController;
+        }
         mListener = listener;
         try {
             token.linkToDeath(this, 0);
@@ -318,11 +330,12 @@
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
 
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.close();
+            mSensorController.close();
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -402,12 +415,12 @@
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
                     displayId);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -429,12 +442,12 @@
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
                     displayId);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -456,11 +469,11 @@
                                 + "virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -490,12 +503,12 @@
                             + screenSize);
         }
 
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createTouchscreen(deviceName, vendorId, productId,
                     deviceToken, displayId, screenSize);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -505,92 +518,92 @@
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to unregister this input device");
 
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.unregisterInputDevice(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public int getInputDeviceId(IBinder token) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.getInputDeviceId(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
 
     @Override // Binder call
     public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendDpadKeyEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendKeyEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendButtonEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendTouchEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendRelativeEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendScrollEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public PointF getCursorPosition(IBinder token) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.getCursorPosition(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -600,7 +613,7 @@
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to unregister this input device");
 
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
@@ -609,7 +622,50 @@
                 }
             }
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void createVirtualSensor(
+            @NonNull IBinder deviceToken,
+            @NonNull VirtualSensorConfig config) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to create a virtual sensor");
+        Objects.requireNonNull(config);
+        Objects.requireNonNull(deviceToken);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSensorController.createSensor(deviceToken, config);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void unregisterSensor(@NonNull IBinder token) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to unregister a virtual sensor");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSensorController.unregisterSensor(token);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to send a virtual sensor event");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mSensorController.sendSensorEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -626,9 +682,11 @@
             fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
         mInputController.dump(fout);
+        mSensorController.dump(fout);
     }
 
-    GenericWindowPolicyController createWindowPolicyController() {
+    GenericWindowPolicyController createWindowPolicyController(
+            @NonNull List<String> displayCategories) {
         synchronized (mVirtualDeviceLock) {
             final GenericWindowPolicyController gwpc =
                     new GenericWindowPolicyController(FLAG_SECURE,
@@ -643,7 +701,8 @@
                             this::onEnteringPipBlocked,
                             this::onActivityBlocked,
                             this::onSecureWindowShown,
-                            mAssociationInfo.getDeviceProfile());
+                            mAssociationInfo.getDeviceProfile(),
+                            displayCategories);
             gwpc.registerRunningAppsChangedListener(/* listener= */ this);
             return gwpc;
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index a8797a0..2b62f69 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -32,6 +32,7 @@
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
@@ -280,7 +281,22 @@
                             @Override
                             public void onClose(int associationId) {
                                 synchronized (mVirtualDeviceManagerLock) {
-                                    mVirtualDevices.remove(associationId);
+                                    VirtualDeviceImpl removedDevice =
+                                            mVirtualDevices.removeReturnOld(associationId);
+                                    if (removedDevice != null) {
+                                        Intent i = new Intent(
+                                                VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+                                        i.putExtra(
+                                                VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
+                                                removedDevice.getDeviceId());
+                                        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                                        final long identity = Binder.clearCallingIdentity();
+                                        try {
+                                            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+                                        } finally {
+                                            Binder.restoreCallingIdentity(identity);
+                                        }
+                                    }
                                     mAppsOnVirtualDevices.remove(associationId);
                                     if (cameraAccessController != null) {
                                         cameraAccessController.stopObservingIfNeeded();
@@ -332,7 +348,8 @@
             GenericWindowPolicyController gwpc;
             final long token = Binder.clearCallingIdentity();
             try {
-                gwpc = virtualDeviceImpl.createWindowPolicyController();
+                gwpc = virtualDeviceImpl.createWindowPolicyController(
+                    virtualDisplayConfig.getDisplayCategories());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 544dd4e..59024e7 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -73,6 +73,7 @@
 import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @hide
@@ -104,6 +105,9 @@
     @VisibleForTesting
     static final String BUNDLE_CONTENT_DIGEST = "content-digest";
 
+    static final String APEX_PRELOAD_LOCATION = "/system/apex/";
+    static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
+
     // used for indicating any type of error during MBA measurement
     static final int MBA_STATUS_ERROR = 0;
     // used for indicating factory condition preloads
@@ -485,24 +489,10 @@
                         Integer algorithmId = entry.getKey();
                         byte[] contentDigest = entry.getValue();
 
-                        // TODO(b/259348134): consider refactoring the following to a helper method
-                        switch (algorithmId) {
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
-                                pw.print("CHUNKED_SHA256:");
-                                break;
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
-                                pw.print("CHUNKED_SHA512:");
-                                break;
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
-                                pw.print("VERITY_CHUNKED_SHA256:");
-                                break;
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
-                                pw.print("SHA256:");
-                                break;
-                            default:
-                                pw.print("UNKNOWN_ALGO_ID(" + algorithmId + "):");
-                        }
+                        pw.print(translateContentDigestAlgorithmIdToString(algorithmId));
+                        pw.print(":");
                         pw.print(HexEncoding.encodeToString(contentDigest, false));
+                        pw.print("\n");
                     }
                 }
 
@@ -529,31 +519,13 @@
                             pw.println("ERROR: Failed to compute package content digest for "
                                     + origPackageFilepath);
                         } else {
-                            // TODO(b/259348134): consider refactoring this to a helper method
                             for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
                                 Integer algorithmId = entry.getKey();
                                 byte[] contentDigest = entry.getValue();
-                                pw.print("|--> Pre-installed package content digest algorithm: ");
-                                switch (algorithmId) {
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
-                                        pw.print("CHUNKED_SHA256");
-                                        break;
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
-                                        pw.print("CHUNKED_SHA512");
-                                        break;
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
-                                        pw.print("VERITY_CHUNKED_SHA256");
-                                        break;
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
-                                        pw.print("SHA256");
-                                        break;
-                                    default:
-                                        pw.print("UNKNOWN");
-                                }
-                                pw.print("\n");
-                                pw.print("|--> Pre-installed package content digest: ");
-                                pw.print(HexEncoding.encodeToString(contentDigest, false));
-                                pw.print("\n");
+                                pw.println("|--> Pre-installed package content digest: "
+                                        + HexEncoding.encodeToString(contentDigest, false));
+                                pw.println("|--> Pre-installed package content digest algorithm: "
+                                        + translateContentDigestAlgorithmIdToString(algorithmId));
                             }
                         }
                     }
@@ -735,7 +707,6 @@
                         pw.print(packageName + ","
                                 + packageInfo.getLongVersionCode() + ",");
                         printPackageMeasurements(packageInfo, pw);
-                        pw.print("\n");
 
                         if (verbose) {
                             ModuleInfo moduleInfo;
@@ -798,7 +769,6 @@
                             pw.print(packageInfo.packageName + ",");
                             pw.print(packageInfo.getLongVersionCode() + ",");
                             printPackageMeasurements(packageInfo, pw);
-                            pw.print("\n");
 
                             if (verbose) {
                                 printModuleDetails(module, pw);
@@ -854,7 +824,6 @@
                         pw.print(packageInfo.packageName + ",");
                         pw.print(packageInfo.getLongVersionCode() + ",");
                         printPackageMeasurements(packageInfo, pw);
-                        pw.print("\n");
 
                         if (verbose) {
                             printAppDetails(packageInfo, printLibraries, pw);
@@ -1075,6 +1044,21 @@
         FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
     }
 
+    private String translateContentDigestAlgorithmIdToString(int algorithmId) {
+        switch (algorithmId) {
+            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+                return "CHUNKED_SHA256";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+                return "CHUNKED_SHA512";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                return "VERITY_CHUNKED_SHA256";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+                return "SHA256";
+            default:
+                return "UNKNOWN_ALGO_ID(" + algorithmId + ")";
+        }
+    }
+
     @NonNull
     private List<PackageInfo> getCurrentInstalledApexs() {
         List<PackageInfo> results = new ArrayList<>();
@@ -1110,18 +1094,22 @@
         }
     }
 
-    // TODO(b/259349011): Need to be more robust against package name mismatch in the filename
+    @NonNull
     private String getOriginalApexPreinstalledLocation(String packageName,
                                                    String currentInstalledLocation) {
-        if (currentInstalledLocation.contains("/decompressed/")) {
-            String resultPath = "system/apex" + packageName + ".capex";
-            File f = new File(resultPath);
-            if (f.exists()) {
-                return resultPath;
+        // get a listing of all apex files in /system/apex/
+        Set<String> originalApexs = Stream.of(new File(APEX_PRELOAD_LOCATION).listFiles())
+                                        .filter(f -> !f.isDirectory())
+                                        .map(File::getName)
+                                        .collect(Collectors.toSet());
+
+        for (String originalApex : originalApexs) {
+            if (originalApex.startsWith(packageName)) {
+                return APEX_PRELOAD_LOCATION + originalApex;
             }
-            return "/system/apex/" + packageName + ".next.capex";
         }
-        return "/system/apex" + packageName + "apex";
+
+        return APEX_PRELOAD_LOCATION_ERROR;
     }
 
     /**
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 104d10d..540ed4c 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -302,6 +302,7 @@
                                     getContext(), soundUri);
                             if (sfx != null) {
                                 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                                sfx.preferBuiltinDevice(true);
                                 sfx.play();
                             }
                         }
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 02c6ca2..27ee627 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -91,7 +91,7 @@
     private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
     private static final int DEFAULT_QUOTA_KB = 10 * 1024;
     private static final int DEFAULT_QUOTA_PERCENT = 10;
-    private static final int DEFAULT_RESERVE_PERCENT = 10;
+    private static final int DEFAULT_RESERVE_PERCENT = 0;
     private static final int QUOTA_RESCAN_MILLIS = 5000;
 
     private static final boolean PROFILE_DUMP = false;
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index b56d1fc..b4ab254 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -447,6 +447,13 @@
                 thread.start();
                 break;
             case LEVEL_FACTORY_RESET:
+                // Before the completion of Reboot, if any crash happens then PackageWatchdog
+                // escalates to next level i.e. factory reset, as they happen in separate threads.
+                // Adding a check to prevent factory reset to execute before above reboot completes.
+                // Note: this reboot property is not persistent resets after reboot is completed.
+                if (isRebootPropertySet()) {
+                    break;
+                }
                 SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
                 runnable = new Runnable() {
                     @Override
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dd3020a..4539e9e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -33,6 +33,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
 import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
@@ -227,7 +228,8 @@
     private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
     private static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
 
-    private static final boolean DEBUG_SHORT_SERVICE = DEBUG_SERVICE;
+    // STOPSHIP(b/260012573) turn it off.
+    private static final boolean DEBUG_SHORT_SERVICE = true; // DEBUG_SERVICE;
 
     private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
 
@@ -1286,6 +1288,8 @@
                 return;
             }
 
+            maybeStopShortFgsTimeoutLocked(service);
+
             final int uid = service.appInfo.uid;
             final String packageName = service.name.getPackageName();
             final String serviceName = service.name.getClassName();
@@ -1469,6 +1473,8 @@
                 }
             }
 
+            maybeStopShortFgsTimeoutLocked(r);
+
             final int uid = r.appInfo.uid;
             final String packageName = r.name.getPackageName();
             final String serviceName = r.name.getClassName();
@@ -1836,6 +1842,14 @@
                         +  String.format("0x%08X", manifestType)
                         + " in service element of manifest file");
                 }
+                if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) != 0
+                        && foregroundServiceType != FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) {
+                    Slog.w(TAG_SERVICE, "startForeground(): FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+                            + " is combined with other types. SHORT_SERVICE will be ignored.");
+                    // In this case, the service will be handled as a non-short, regular FGS
+                    // anyway, so we just remove the SHORT_SERVICE type.
+                    foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+                }
             }
 
             boolean alreadyStartedOp = false;
@@ -1886,14 +1900,51 @@
 
                 int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
                 if (!ignoreForeground) {
-                    // TODO(short-service): There's a known long-standing bug that allows
-                    // a abound service to become "foreground" if setForeground() is called
-                    // (without actually "starting" it).
-                    // Unfortunately we can't just "fix" it because some apps are relying on it,
-                    // but this will cause a problem to short-fgs, so we should disallow it if
-                    // this happens and the type is SHORT_SERVICE.
-                    //
-                    // OTOH, if a valid short-service (which has to be "started"), happens to
+                    if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
+                            && !r.startRequested) {
+                        // There's a long standing bug that allows a bound service to become
+                        // a foreground service *even when it's not started*.
+                        // Unfortunately, there are apps relying on this behavior, so we can't just
+                        // suddenly disallow it.
+                        // However, this would be very problematic if used with a short-FGS, so we
+                        // explicitly disallow this combination.
+                        // TODO(short-service): Change to another exception type?
+                        throw new IllegalStateException(
+                                "startForeground(SHORT_SERVICE) called on a service that's not"
+                                + " started.");
+                    }
+                    // If the service is already an FGS, and the type is changing, then we
+                    // may need to do some extra work here.
+                    if (r.isForeground && (r.foregroundServiceType != foregroundServiceType)) {
+                        // TODO(short-service): Consider transitions:
+                        //   A. Short -> other types:
+                        //     Apply the BG restriction again. Don't just allow it.
+                        //     i.e. unless the app is in a situation where it's allowed to start
+                        //     a FGS, this transition shouldn't be allowed.
+                        //     ... But think about it more, there may be a case this should be
+                        //     allowed.
+                        //
+                        //     If the transition is allowed, stop the timeout.
+                        //     If the transition is _not_ allowed... keep the timeout?
+                        //
+                        //   B. Short -> Short:
+                        //     Allowed, but the timeout won't reset. The original timeout is used.
+                        //   C. Other -> short:
+                        //     This should always be allowed.
+                        //     A timeout should start.
+
+                        // For now, let's just disallow transition from / to SHORT_SERVICE.
+                        final boolean isNewTypeShortFgs =
+                                foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+                        if (r.isShortFgs() != isNewTypeShortFgs) {
+                            // TODO(short-service): We should (probably) allow it.
+                            throw new IllegalArgumentException(
+                                    "setForeground(): Changing foreground service type from / to "
+                                    + " SHORT_SERVICE is now allowed");
+                        }
+                    }
+
+                    // If a valid short-service (which has to be "started"), happens to
                     // also be bound, then we still _will_ apply a timeout, because it still has
                     // to be stopped.
                     if (r.mStartForegroundCount == 0) {
@@ -1932,24 +1983,6 @@
                         // on the same sarvice after it's created, regardless of whether
                         // stopForeground() has been called or not.
 
-                        // TODO(short-service): Consider transitions:
-                        //   A. Short -> other types:
-                        //     Apply the BG restriction again. Don't just allow it.
-                        //     i.e. unless the app is in a situation where it's allowed to start
-                        //     a FGS, this transition shouldn't be allowed.
-                        //     ... But think about it more, there may be a case this should be
-                        //     allowed.
-                        //
-                        //     If the transition is allowed, stop the timeout.
-                        //     If the transition is _not_ allowed... keep the timeout?
-                        //
-                        //   B. Short -> Short:
-                        //     This should be the same as case A
-                        //     If this is allowed, the new timeout should start.
-                        //   C. Other -> short:
-                        //     This should always be allowed.
-                        //     A timeout should start.
-
                         // The second or later time startForeground() is called after service is
                         // started. Check for app state again.
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
@@ -2106,8 +2139,11 @@
                     mAm.notifyPackageUse(r.serviceInfo.packageName,
                             PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
 
-                    // TODO(short-service): Start counting a timeout.
-
+                    // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
+                    // already short-fgs.
+                    // In that case, because ShortFgsInfo is already set, this method
+                    // will be noop.
+                    maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
                 } else {
                     if (DEBUG_FOREGROUND_SERVICE) {
                         Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2141,7 +2177,7 @@
                     decActiveForegroundAppLocked(smap, r);
                 }
 
-                // TODO(short-service): Stop the timeout. (any better place to do it?)
+                maybeStopShortFgsTimeoutLocked(r);
 
                 // Adjust notification handling before setting isForeground to false, because
                 // that state is relevant to the notification policy side.
@@ -2886,6 +2922,90 @@
         psr.setHasReportedForegroundServices(anyForeground);
     }
 
+    void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+    }
+
+    /**
+     * If {@code sr} is of a short-fgs, start a short-FGS timeout.
+     */
+    private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
+        if (!sr.isShortFgs()) {
+            return;
+        }
+        if (DEBUG_SHORT_SERVICE) {
+            Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
+        }
+        if (sr.hasShortFgsInfo()) {
+            sr.getShortFgsInfo().update();
+        } else {
+            sr.setShortFgsInfo(SystemClock.uptimeMillis());
+        }
+        unscheduleShortFgsTimeoutLocked(sr); // Do it just in case
+
+        final Message msg = mAm.mHandler.obtainMessage(
+                ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+        mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
+    }
+
+    /**
+     * Stop the timeout for a ServiceRecord, if it's of a short-FGS.
+     */
+    private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
+        if (!sr.isShortFgs()) {
+            return;
+        }
+        if (DEBUG_SHORT_SERVICE) {
+            Slog.i(TAG_SERVICE, "Stop short FGS timeout: " + sr);
+        }
+        sr.clearShortFgsInfo();
+        unscheduleShortFgsTimeoutLocked(sr);
+    }
+
+    void onShortFgsTimeout(ServiceRecord sr) {
+        synchronized (mAm) {
+            if (!sr.shouldTriggerShortFgsTimeout()) {
+                return;
+            }
+            Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+            try {
+                sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId());
+            } catch (RemoteException e) {
+                // TODO(short-service): Anything to do here?
+            }
+            // Schedule the ANR timeout.
+            final Message msg = mAm.mHandler.obtainMessage(
+                    ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+            mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
+        }
+    }
+
+    void onShortFgsAnrTimeout(ServiceRecord sr) {
+        final String reason = "A foreground service of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+                + " did not stop within a timeout: " + sr.getComponentName();
+
+        final TimeoutRecord tr = TimeoutRecord.forShortFgsTimeout(reason);
+
+        // TODO(short-service): TODO Add SHORT_FGS_TIMEOUT to AnrLatencyTracker
+        tr.mLatencyTracker.waitingOnAMSLockStarted();
+        synchronized (mAm) {
+            tr.mLatencyTracker.waitingOnAMSLockEnded();
+
+            if (!sr.shouldTriggerShortFgsAnr()) {
+                return;
+            }
+
+            final String message = "Short FGS ANR'ed: " + sr;
+            if (DEBUG_SHORT_SERVICE) {
+                Slog.wtf(TAG_SERVICE, message);
+            } else {
+                Slog.e(TAG_SERVICE, message);
+            }
+            mAm.appNotResponding(sr.app, tr);
+        }
+    }
+
     private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
         psr.mAllowlistManager = false;
         for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -2897,6 +3017,7 @@
         }
     }
 
+    // TODO(short-service): Hmm what is it? Should we stop the timeout here?
     private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
         final ProcessServiceRecord psr = service.app.mServices;
         psr.stopService(service);
@@ -4178,7 +4299,7 @@
     /**
      * Reschedule service restarts based on if the extra delays are enabled or not.
      *
-     * @param prevEnable The previous state of whether or not it's enabled.
+     * @param prevEnabled The previous state of whether or not it's enabled.
      * @param curEnabled The current state of whether or not it's enabled.
      * @param now The uptimeMillis
      */
@@ -4863,13 +4984,6 @@
             Slog.i(TAG, "Bring down service for " + debugReason + " :" + r.toString());
         }
 
-        // TODO(short-service): Hmm, when the app stops a short-fgs, we should stop the timeout
-        // here.
-        // However we have a couple if's here and if these conditions are met, we stop here
-        // without bringing down the service.
-        // We need to make sure this can't be used (somehow) to keep having a short-FGS running
-        // while having the timeout stopped.
-
         if (isServiceNeededLocked(r, knowConn, hasConn)) {
             return;
         }
@@ -4886,6 +5000,13 @@
         //Slog.i(TAG, "Bring down service:");
         //r.dump("  ");
 
+        if (r.isShortFgs()) {
+            // FGS can be stopped without the app calling stopService() or stopSelf(),
+            // due to force-app-standby, or from Task Manager.
+            Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
+            maybeStopShortFgsTimeoutLocked(r);
+        }
+
         // Report to all of the connections that the service is no longer
         // available.
         ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 046403d..2d69667 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -954,7 +954,7 @@
     static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;
 
     /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
-    public static volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
+    public volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
 
     /**
      * If a "short service" doesn't finish within this after the timeout (
@@ -967,7 +967,7 @@
     static final long DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION = 5_000;
 
     /** @see #KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION */
-    public static volatile long mShortFgsProcStateExtraWaitDuration =
+    public volatile long mShortFgsProcStateExtraWaitDuration =
             DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;
 
     /**
@@ -983,7 +983,7 @@
     static final long DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
 
     /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
-    public static volatile long mShortFgsAnrExtraWaitDuration =
+    public volatile long mShortFgsAnrExtraWaitDuration =
             DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
 
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c779ea9..35b46c1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -206,7 +206,7 @@
 import android.app.SyncNotedAppOp;
 import android.app.WaitResult;
 import android.app.assist.ActivityId;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManager;
 import android.app.compat.CompatChanges;
 import android.app.job.JobParameters;
@@ -1563,6 +1563,8 @@
     static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
     static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
     static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
+    static final int SERVICE_SHORT_FGS_TIMEOUT_MSG = 76;
+    static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 77;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1897,6 +1899,12 @@
                     mBindServiceEventListeners.forEach(l ->
                             l.onBindingService((String) msg.obj, msg.arg1));
                 } break;
+                case SERVICE_SHORT_FGS_TIMEOUT_MSG: {
+                    mServices.onShortFgsTimeout((ServiceRecord) msg.obj);
+                } break;
+                case SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG: {
+                    mServices.onShortFgsAnrTimeout((ServiceRecord) msg.obj);
+                } break;
             }
         }
     }
@@ -5179,7 +5187,8 @@
                              PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
             try {
                 thread.scheduleCreateBackupAgent(backupTarget.appInfo,
-                        backupTarget.backupMode, backupTarget.userId, backupTarget.operationType);
+                        backupTarget.backupMode, backupTarget.userId,
+                        backupTarget.backupDestination);
             } catch (Exception e) {
                 Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
                 badApp = true;
@@ -6141,6 +6150,7 @@
     /**
      * This can be called with or without the global lock held.
      */
+    @PermissionMethod(anyOf = true)
     private void enforceCallingHasAtLeastOnePermission(String func, String... permissions) {
         for (String permission : permissions) {
             if (checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
@@ -13129,7 +13139,7 @@
     // instantiated.  The backup agent will invoke backupAgentCreated() on the
     // activity manager to announce its creation.
     public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
-            @OperationType int operationType) {
+            @BackupDestination int backupDestination) {
         if (DEBUG_BACKUP) {
             Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
                     + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13196,7 +13206,7 @@
                         + app.packageName + ": " + e);
             }
 
-            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, operationType);
+            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
             ComponentName hostingName =
                     (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL)
                             ? new ComponentName(app.packageName, app.backupAgentName)
@@ -13238,7 +13248,7 @@
                 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
                 try {
                     thread.scheduleCreateBackupAgent(app, backupMode, targetUserId,
-                            operationType);
+                            backupDestination);
                 } catch (RemoteException e) {
                     // Will time out on the backup manager side
                 }
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index d419856..0b056d7 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -16,8 +16,7 @@
 
 package com.android.server.am;
 
-import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.content.pm.ApplicationInfo;
 
 /** @hide */
@@ -32,16 +31,16 @@
     final ApplicationInfo appInfo;         // information about BackupAgent's app
     final int userId;                      // user for which backup is performed
     final int backupMode;                  // full backup / incremental / restore
-    @OperationType  final int operationType; // see BackupManager#OperationType
+    @BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
     ProcessRecord app;                     // where this agent is running or null
 
     // ----- Implementation -----
 
-    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _operationType) {
+    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
         appInfo = _appInfo;
         backupMode = _backupMode;
         userId = _userId;
-        operationType = _operationType;
+        backupDestination = _backupDestination;
     }
 
     public String toString() {
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 04ba757..1eebd01 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,6 +153,11 @@
             "bcast_extra_running_urgent_process_queues";
     private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
 
+    public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
+    private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
+            "bcast_max_consecutive_urgent_dispatches";
+    private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
+
     /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
      * to dispatch to a "running" process queue before we retire them back to
@@ -333,6 +338,9 @@
             EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
                     KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES,
                     DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+            MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
+                    KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+                    DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
             MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
                     DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
             MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0f9c775..66d7fc9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -147,6 +147,12 @@
     private boolean mActiveViaColdStart;
 
     /**
+     * Number of consecutive urgent broadcasts that have been dispatched
+     * since the last non-urgent dispatch.
+     */
+    private int mActiveCountConsecutiveUrgent;
+
+    /**
      * Count of pending broadcasts of these various flavors.
      */
     private int mCountForeground;
@@ -546,19 +552,47 @@
      * {@link #isEmpty()} being false.
      */
     SomeArgs removeNextBroadcast() {
-        ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+        final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+        if (queue == mPendingUrgent) {
+            mActiveCountConsecutiveUrgent++;
+        } else {
+            mActiveCountConsecutiveUrgent = 0;
+        }
         return queue.removeFirst();
     }
 
     @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
-        if (!mPendingUrgent.isEmpty()) {
-            return mPendingUrgent;
-        } else if (!mPending.isEmpty()) {
-            return mPending;
+        ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
+        ArrayDeque<SomeArgs> nextNormal = null;
+        if (!mPending.isEmpty()) {
+            nextNormal = mPending;
         } else if (!mPendingOffload.isEmpty()) {
-            return mPendingOffload;
+            nextNormal = mPendingOffload;
         }
-        return null;
+        // nothing urgent pending, no further decisionmaking
+        if (nextUrgent == null) {
+            return nextNormal;
+        }
+        // nothing but urgent pending, also no further decisionmaking
+        if (nextNormal == null) {
+            return nextUrgent;
+        }
+
+        // Starvation mitigation: although we prioritize urgent broadcasts by default,
+        // we allow non-urgent deliveries to make steady progress even if urgent
+        // broadcasts are arriving faster than they can be dispatched.
+        //
+        // We do not try to defer to the next non-urgent broadcast if that broadcast
+        // is ordered and still blocked on delivery to other recipients.
+        final SomeArgs nextNormalArgs = nextNormal.peekFirst();
+        final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
+        final int nextNormalIndex = nextNormalArgs.argi1;
+        final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
+        final boolean canTakeNormal =
+                mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
+                        && rNormal.enqueueTime <= rUrgent.enqueueTime
+                        && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
+        return canTakeNormal ? nextNormal : nextUrgent;
     }
 
     /**
@@ -710,6 +744,18 @@
         }
     }
 
+    private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
+        final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
+
+        // We might be blocked waiting for other receivers to finish,
+        // typically for an ordered broadcast or priority traunches
+        if (r.terminalCount < blockedUntilTerminalCount
+                && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Update {@link #getRunnableAt()} if it's currently invalidated.
      */
@@ -718,13 +764,11 @@
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
-            final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
             final long runnableAt = r.enqueueTime;
 
-            // We might be blocked waiting for other receivers to finish,
-            // typically for an ordered broadcast or priority traunches
-            if (r.terminalCount < blockedUntilTerminalCount
-                    && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+            // If we're specifically queued behind other ordered dispatch activity,
+            // we aren't runnable yet
+            if (blockedOnOrderedDispatch(r, index)) {
                 mRunnableAt = Long.MAX_VALUE;
                 mRunnableAtReason = REASON_BLOCKED;
                 return;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 8082e45..66a8bab 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1818,21 +1818,31 @@
                 newAdj = PERCEPTIBLE_APP_ADJ;
                 newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
 
-            } else if (psr.hasForegroundServices() && !psr.hasNonShortForegroundServices()) {
-                // For short FGS.
-                adjType = "fg-service-short";
-                // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ (which uses MEDIUM_APP_ADJ + 1)
-                // from short-FGS.
-                // (We use +1 and +2, not +0 and +1, to be consistent with the following
-                // RECENT_FOREGROUND_APP_ADJ tweak)
-                newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
+            } else if (psr.hasForegroundServices()) {
+                // If we get here, hasNonShortForegroundServices() must be false.
 
-                // Short-FGS gets a below-BFGS procstate, so it can't start another FGS from it.
-                newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+                // TODO(short-service): Proactively run OomAjudster when the grace period finish.
+                if (psr.areAllShortForegroundServicesProcstateTimedOut(now)) {
+                    // All the short-FGSes within this process are timed out. Don't promote to FGS.
+                    // TODO(short-service): Should we set some unique oom-adj to make it detectable,
+                    // in a long trace?
+                } else {
+                    // For short FGS.
+                    adjType = "fg-service-short";
+                    // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
+                    // (which uses MEDIUM_APP_ADJ + 1)
+                    // from short-FGS.
+                    // (We use +1 and +2, not +0 and +1, to be consistent with the following
+                    // RECENT_FOREGROUND_APP_ADJ tweak)
+                    newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
 
-                // Same as EJ, we explicitly grant network access to short FGS,
-                // even when battery saver or data saver is enabled.
-                capabilityFromFGS |= PROCESS_CAPABILITY_NETWORK;
+                    // Short-FGS gets a below-BFGS procstate, so it can't start another FGS from it.
+                    newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+
+                    // Same as EJ, we explicitly grant network access to short FGS,
+                    // even when battery saver or data saver is enabled.
+                    capabilityFromFGS |= PROCESS_CAPABILITY_NETWORK;
+                }
             }
 
             if (adjType != null) {
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 13264db..df442e8 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -175,6 +175,9 @@
         }
     }
 
+    /**
+     * @return true if this process has any foreground services (even timed-out short-FGS)
+     */
     boolean hasForegroundServices() {
         return mHasForegroundServices;
     }
@@ -224,6 +227,33 @@
         return mFgServiceTypes != ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
     }
 
+    /**
+     * @return if this process:
+     * - has at least one short-FGS
+     * - has no other types of FGS
+     * - and all the short-FGSes are procstate-timed out.
+     */
+    boolean areAllShortForegroundServicesProcstateTimedOut(long nowUptime) {
+        if (!mHasForegroundServices) { // Process has no FGS?
+            return false;
+        }
+        if (hasNonShortForegroundServices()) {  // Any non-short FGS running?
+            return false;
+        }
+        // Now we need to look at all short-FGS within the process and see if all of them are
+        // procstate-timed-out or not.
+        for (int i = mServices.size() - 1; i >= 0; i--) {
+            final ServiceRecord sr = mServices.valueAt(i);
+            if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
+                continue;
+            }
+            if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     int getReportedForegroundServiceTypes() {
         return mRepFgServiceTypes;
     }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0468152..547c20b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -315,6 +315,87 @@
     final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
                             // start() arguments that haven't yet been delivered.
 
+    /**
+     * Information specific to "SHORT_SERVICE" FGS.
+     */
+    class ShortFgsInfo {
+        /** Time FGS started */
+        private final long mStartTime;
+
+        /**
+         * Copied from {@link #mStartForegroundCount}. If this is different from the parent's,
+         * that means this instance is stale.
+         */
+        private int mStartForegroundCount;
+
+        /** Service's "start ID" when this short-service started. */
+        private int mStartId;
+
+        ShortFgsInfo(long startTime) {
+            mStartTime = startTime;
+            update();
+        }
+
+        /**
+         * Update {@link #mStartForegroundCount} and {@link #mStartId}.
+         * (but not {@link #mStartTime})
+         */
+        public void update() {
+            this.mStartForegroundCount = ServiceRecord.this.mStartForegroundCount;
+            this.mStartId = getLastStartId();
+        }
+
+        long getStartTime() {
+            return mStartTime;
+        }
+
+        int getStartForegroundCount() {
+            return mStartForegroundCount;
+        }
+
+        int getStartId() {
+            return mStartId;
+        }
+
+        /**
+         * @return whether this {@link ShortFgsInfo} is still "current" or not -- i.e.
+         * it's "start foreground count" is the same as that of the ServiceRecord's.
+         *
+         * Note, we do _not_ check the "start id" here, because the start id increments if the
+         * app calls startService() or startForegroundService() on the same service,
+         * but that will _not_ update the ShortFgsInfo, and will not extend the timeout.
+         *
+         * TODO(short-service): Make sure, calling startService will not extend or remove the
+         * timeout, in CTS.
+         */
+        boolean isCurrent() {
+            return this.mStartForegroundCount == ServiceRecord.this.mStartForegroundCount;
+        }
+
+        /** Time when Service.onTimeout() should be called */
+        long getTimeoutTime() {
+            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration;
+        }
+
+        /** Time when the procstate should be lowered. */
+        long getProcStateDemoteTime() {
+            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
+                    + ams.mConstants.mShortFgsProcStateExtraWaitDuration;
+        }
+
+        /** Time when the app should be declared ANR. */
+        long getAnrTime() {
+            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
+                    + ams.mConstants.mShortFgsAnrExtraWaitDuration;
+        }
+    }
+
+    /**
+     * Keep track of short-fgs specific information. This field gets cleared when the timeout
+     * stops.
+     */
+    private ShortFgsInfo mShortFgsInfo;
+
     void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
         final int N = list.size();
         for (int i=0; i<N; i++) {
@@ -456,6 +537,8 @@
             }
         }
         proto.end(token);
+
+        // TODO(short-service) Add FGS info
     }
 
     void dump(PrintWriter pw, String prefix) {
@@ -508,8 +591,25 @@
         }
         if (isForeground || foregroundId != 0) {
             pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
-                    pw.print(" foregroundId="); pw.print(foregroundId);
-                    pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+            pw.print(" foregroundId="); pw.print(foregroundId);
+            pw.printf(" types=%08X", foregroundServiceType);
+            pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+
+            if (isShortFgs() && mShortFgsInfo != null) {
+                pw.print(prefix); pw.print("isShortFgs=true");
+                pw.print(" startId="); pw.print(mShortFgsInfo.getStartId());
+                pw.print(" startForegroundCount=");
+                pw.print(mShortFgsInfo.getStartForegroundCount());
+                pw.print(" startTime=");
+                TimeUtils.formatDuration(mShortFgsInfo.getStartTime(), now, pw);
+                pw.print(" timeout=");
+                TimeUtils.formatDuration(mShortFgsInfo.getTimeoutTime(), now, pw);
+                pw.print(" demoteTime=");
+                TimeUtils.formatDuration(mShortFgsInfo.getProcStateDemoteTime(), now, pw);
+                pw.print(" anrTime=");
+                TimeUtils.formatDuration(mShortFgsInfo.getAnrTime(), now, pw);
+                pw.println();
+            }
         }
         if (mIsFgsDelegate) {
             pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
@@ -590,6 +690,32 @@
         }
     }
 
+    /** Used only for tests */
+    private ServiceRecord(ActivityManagerService ams) {
+        this.ams = ams;
+        name = null;
+        instanceName = null;
+        shortInstanceName = null;
+        definingPackageName = null;
+        definingUid = 0;
+        intent = null;
+        serviceInfo = null;
+        userId = 0;
+        packageName = null;
+        processName = null;
+        permission = null;
+        exported = false;
+        restarter = null;
+        createRealTime = 0;
+        isSdkSandbox = false;
+        sdkSandboxClientAppUid = 0;
+        sdkSandboxClientAppPackage = null;
+    }
+
+    public static ServiceRecord newEmptyInstanceForTest(ActivityManagerService ams) {
+        return new ServiceRecord(ams);
+    }
+
     ServiceRecord(ActivityManagerService ams, ComponentName name,
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
@@ -1238,4 +1364,66 @@
         return isForeground
                 && (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
     }
+
+    public ShortFgsInfo getShortFgsInfo() {
+        return isShortFgs() ? mShortFgsInfo : null;
+    }
+
+    /**
+     * Call it when a short FGS starts.
+     */
+    public void setShortFgsInfo(long uptimeNow) {
+        this.mShortFgsInfo = new ShortFgsInfo(uptimeNow);
+    }
+
+    /** @return whether {@link #mShortFgsInfo} is set or not. */
+    public boolean hasShortFgsInfo() {
+        return mShortFgsInfo != null;
+    }
+
+    /**
+     * Call it when a short FGS stops.
+     */
+    public void clearShortFgsInfo() {
+        this.mShortFgsInfo = null;
+    }
+
+    /**
+     * @return true if it's a short FGS that's still up and running, and should be timed out.
+     */
+    public boolean shouldTriggerShortFgsTimeout() {
+        if (!isAppAlive()) {
+            return false;
+        }
+        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+                || !mShortFgsInfo.isCurrent()) {
+            return false;
+        }
+        return mShortFgsInfo.getTimeoutTime() < SystemClock.uptimeMillis();
+    }
+
+    /**
+     * @return true if it's a short FGS that's still up and running, and should be declared
+     * an ANR.
+     */
+    public boolean shouldTriggerShortFgsAnr() {
+        if (!isAppAlive()) {
+            return false;
+        }
+        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+                || !mShortFgsInfo.isCurrent()) {
+            return false;
+        }
+        return mShortFgsInfo.getAnrTime() < SystemClock.uptimeMillis();
+    }
+
+    private boolean isAppAlive() {
+        if (app == null) {
+            return false;
+        }
+        if (app.getThread() == null || app.isKilled() || app.isKilledByAm()) {
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index f16347f..f22624c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -104,6 +104,7 @@
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
+        DeviceConfig.NAMESPACE_HDMI_CONTROL
     };
 
     private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b92c163..a954164 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -338,7 +338,18 @@
                                     + " and userId " + userId);
                             break;
                         }
+                        if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
+                            mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
+                        }
                         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+                        if (isLoading) {
+                            int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
+                            loadingBoostDuration = loadingBoostDuration > 0 ? loadingBoostDuration
+                                    : LOADING_BOOST_MAX_DURATION;
+                            mHandler.sendMessageDelayed(
+                                    mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE),
+                                    loadingBoostDuration);
+                        }
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index 0ba4d8c..82840ee 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -9,6 +9,23 @@
       ]
     },
     {
+      "name": "CtsStatsdAtomHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.cts.statsdatom.gamemanager"
+        }
+      ],
+      "file_patterns": [
+        "(/|^)GameManagerService.java"
+      ]
+    },
+    {
       "name": "FrameworksMockingServicesTests",
       "options": [
         {
@@ -18,6 +35,23 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "FrameworksCoreGameManagerTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.app"
+        }
+      ],
+      "file_patterns": [
+        "(/|^)GameManagerService.java", "(/|^)GameManagerSettings.java"
+      ]
     }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
similarity index 97%
rename from services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
rename to services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index f6fff35..8d1da71 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,7 +20,7 @@
 import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager.opRestrictsRead;
 
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -56,7 +56,7 @@
  * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
  * In the future this class will also include mode callbacks and op restrictions.
  */
-public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
+public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface {
 
     static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
 
@@ -84,9 +84,9 @@
     private static final int UID_ANY = -2;
 
 
-    LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
-            @NonNull Object lock, Handler handler, Context context,
-            SparseArray<int[]> switchedOps) {
+    AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
+                              @NonNull Object lock, Handler handler, Context context,
+                              SparseArray<int[]> switchedOps) {
         this.mPersistenceScheduler = persistenceScheduler;
         this.mLock = lock;
         this.mHandler = handler;
@@ -456,7 +456,7 @@
             final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
             if (reportedPackageNames == null) {
                 mHandler.sendMessage(PooledLambda.obtainMessage(
-                        LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+                        AppOpsCheckingServiceImpl::notifyOpChanged,
                         this, callback, code, uid, (String) null));
 
             } else {
@@ -464,7 +464,7 @@
                 for (int j = 0; j < reportedPackageCount; j++) {
                     final String reportedPackageName = reportedPackageNames.valueAt(j);
                     mHandler.sendMessage(PooledLambda.obtainMessage(
-                            LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+                            AppOpsCheckingServiceImpl::notifyOpChanged,
                             this, callback, code, uid, reportedPackageName));
                 }
             }
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
new file mode 100644
index 0000000..d8d0d48
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -0,0 +1,209 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
+ */
+public interface AppOpsCheckingServiceInterface {
+    /**
+     * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
+     * Returns an empty SparseIntArray if nothing is set.
+     * @param uid for which we need the app-ops and their modes.
+     */
+    SparseIntArray getNonDefaultUidModes(int uid);
+
+    /**
+     * Returns the app-op mode for a particular app-op of a uid.
+     * Returns default op mode if the op mode for particular uid and op is not set.
+     * @param uid user id for which we need the mode.
+     * @param op app-op for which we need the mode.
+     * @return mode of the app-op.
+     */
+    int getUidMode(int uid, int op);
+
+    /**
+     * Set the app-op mode for a particular uid and op.
+     * The mode is not set if the mode is the same as the default mode for the op.
+     * @param uid user id for which we want to set the mode.
+     * @param op app-op for which we want to set the mode.
+     * @param mode mode for the app-op.
+     * @return true if op mode is changed.
+     */
+    boolean setUidMode(int uid, int op, @Mode int mode);
+
+    /**
+     * Gets the app-op mode for a particular package.
+     * Returns default op mode if the op mode for the particular package is not set.
+     * @param packageName package name for which we need the op mode.
+     * @param op app-op for which we need the mode.
+     * @param userId user id associated with the package.
+     * @return the mode of the app-op.
+     */
+    int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
+
+    /**
+     * Sets the app-op mode for a particular package.
+     * @param packageName package name for which we need to set the op mode.
+     * @param op app-op for which we need to set the mode.
+     * @param mode the mode of the app-op.
+     * @param userId user id associated with the package.
+     *
+     */
+    void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+
+    /**
+     * Stop tracking any app-op modes for a package.
+     * @param packageName Name of the package for which we want to remove all mode tracking.
+     * @param userId user id associated with the package.
+     */
+    boolean removePackage(@NonNull String packageName,  @UserIdInt int userId);
+
+    /**
+     * Stop tracking any app-op modes for this uid.
+     * @param uid user id for which we want to remove all tracking.
+     */
+    void removeUid(int uid);
+
+    /**
+     * Returns true if all uid modes for this uid are
+     * in default state.
+     * @param uid user id
+     */
+    boolean areUidModesDefault(int uid);
+
+    /**
+     * Returns true if all package modes for this package name are
+     * in default state.
+     * @param packageName package name.
+     * @param userId user id associated with the package.
+     */
+    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+
+    /**
+     * Stop tracking app-op modes for all uid and packages.
+     */
+    void clearAllModes();
+
+    /**
+     * Registers changedListener to listen to op's mode change.
+     * @param changedListener the listener that must be trigger on the op's mode change.
+     * @param op op representing the app-op whose mode change needs to be listened to.
+     */
+    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+    /**
+     * Registers changedListener to listen to package's app-op's mode change.
+     * @param changedListener the listener that must be trigger on the mode change.
+     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     */
+    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName);
+
+    /**
+     * Stop the changedListener from triggering on any mode change.
+     * @param changedListener the listener that needs to be removed.
+     */
+    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+     * @param op app-op whose mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+     * @param packageName of package whose app-op's mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+     */
+    void notifyWatchersOfChange(int op, int uid);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param changedListener the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param packageName package name that is associated with the app-op
+     */
+    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+            @Nullable String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed to all packages associated with the uid by
+     * triggering the appropriate change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param onlyForeground true if only watchers that
+     * @param callbackToIgnore callback that should be ignored.
+     */
+    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore);
+
+    /**
+     * TODO: Move hasForegroundWatchers and foregroundOps into this.
+     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param uid for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @return  foregroundOps.
+     */
+    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+
+    /**
+     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param packageName for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @param userId user id associated with the package.
+     * @return foregroundOps.
+     */
+    SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+
+    /**
+     * Dump op mode and package mode listeners and their details.
+     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+     *               app-op, only the watchers for that app-op are dumped.
+     * @param dumpUid uid for which we want to dump op mode watchers.
+     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+     * @param printWriter writer to dump to.
+     */
+    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index adfd2af..af5b07e 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@
 
     private Context mContext;
     private Handler mHandler;
-    private AppOpsServiceInterface mAppOpsServiceInterface;
+    private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
 
     // Map from (Object token) to (int code) to (boolean restricted)
     private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,7 +56,7 @@
             mUserRestrictionExcludedPackageTags = new ArrayMap<>();
 
     public AppOpsRestrictionsImpl(Context context, Handler handler,
-            AppOpsServiceInterface appOpsServiceInterface) {
+            AppOpsCheckingServiceInterface appOpsServiceInterface) {
         mContext = context;
         mHandler = handler;
         mAppOpsServiceInterface = appOpsServiceInterface;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7e00c32..39338c6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,55 +18,22 @@
 
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
 import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.RestrictionBypass;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
 import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
 import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,21 +42,16 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
-import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.Mode;
-import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.AsyncNotedAppOp;
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.SyncNotedAppOp;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -97,15 +59,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -116,22 +74,14 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
-import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -143,59 +93,37 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsStartedCallback;
 import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.PackageList;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Scanner;
-import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Consumer;
 
-public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
+/**
+ * The system service component to {@link AppOpsManager}.
+ */
+public class AppOpsService extends IAppOpsService.Stub {
+
+    private final AppOpsServiceInterface mAppOpsService;
+
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
@@ -204,57 +132,19 @@
      */
     private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
 
-    private static final int NO_VERSION = -1;
-    /** Increment by one every time and add the corresponding upgrade logic in
-     *  {@link #upgradeLocked(int)} below. The first version was 1 */
-    private static final int CURRENT_VERSION = 1;
-
-    // Write at most every 30 minutes.
-    static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
-
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
 
-    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
-            OP_PLAY_AUDIO,
-            OP_RECORD_AUDIO,
-            OP_CAMERA,
-            OP_VIBRATE,
-    };
-
     private static final int MAX_UNFORWARDED_OPS = 10;
-    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
     final Context mContext;
-    final AtomicFile mFile;
     private final @Nullable File mNoteOpCallerStacktracesFile;
     final Handler mHandler;
 
-    /**
-     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
-     * objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
-            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
-    /**
-     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
-     * new objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
-            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
-                    MAX_UNUSED_POOLED_OBJECTS);
-
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
-    @Nullable private final DevicePolicyManagerInternal dpmi =
-            LocalServices.getService(DevicePolicyManagerInternal.class);
-
-    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
     /**
      * Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -281,54 +171,9 @@
 
     boolean mWriteNoteOpsScheduled;
 
-    boolean mWriteScheduled;
-    boolean mFastWriteScheduled;
-    final Runnable mWriteRunner = new Runnable() {
-        public void run() {
-            synchronized (AppOpsService.this) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
-                    @Override protected Void doInBackground(Void... params) {
-                        writeState();
-                        return null;
-                    }
-                };
-                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
-            }
-        }
-    };
-
-    @GuardedBy("this")
-    @VisibleForTesting
-    final SparseArray<UidState> mUidStates = new SparseArray<>();
-
-    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
-    /*
-     * These are app op restrictions imposed per user from various parties.
-     */
-    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
-            new ArrayMap<>();
-
-    /*
-     * These are app op restrictions imposed globally from various parties within the system.
-     */
-    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
-            new ArrayMap<>();
-
-    SparseIntArray mProfileOwners;
-
     private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
             new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
 
-    /**
-      * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
-      * changed
-      */
-    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
-    private ActivityManagerInternal mActivityManagerInternal;
 
     /** Package sampled for message collection in the current session */
     @GuardedBy("this")
@@ -362,545 +207,8 @@
     /** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
     private @Nullable PackageManagerInternal mPackageManagerInternal;
 
-    /** Interface for app-op modes.*/
-    @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
-
-    /** Interface for app-op restrictions.*/
-    @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
-
-    private AppOpsUidStateTracker mUidStateTracker;
-
-    /** Hands the definition of foreground and uid states */
-    @GuardedBy("this")
-    public AppOpsUidStateTracker getUidStateTracker() {
-        if (mUidStateTracker == null) {
-            mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class),
-                    mHandler,
-                    r -> {
-                        synchronized (AppOpsService.this) {
-                            r.run();
-                        }
-                    },
-                    Clock.SYSTEM_CLOCK, mConstants);
-
-            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
-                    this::onUidStateChanged);
-        }
-        return mUidStateTracker;
-    }
-
-    /**
-     * All times are in milliseconds. These constants are kept synchronized with the system
-     * global Settings. Any access to this class or its fields should be done while
-     * holding the AppOpsService lock.
-     */
-    final class Constants extends ContentObserver {
-
-        /**
-         * How long we want for a drop in uid state from top to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
-         */
-        public long TOP_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from foreground to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
-         */
-        public long FG_SERVICE_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from background to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
-         */
-        public long BG_STATE_SETTLE_TIME;
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-        private ContentResolver mResolver;
-
-        public Constants(Handler handler) {
-            super(handler);
-            updateConstants();
-        }
-
-        public void startMonitoring(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
-                    false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
-            String value = mResolver != null ? Settings.Global.getString(mResolver,
-                    Settings.Global.APP_OPS_CONSTANTS) : "";
-
-            synchronized (AppOpsService.this) {
-                try {
-                    mParser.setString(value);
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad app ops settings", e);
-                }
-                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
-                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
-                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
-            }
-        }
-
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
-
-            pw.print("    "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
-            pw.println();
-        }
-    }
-
-    @VisibleForTesting
-    final Constants mConstants;
-
-    @VisibleForTesting
-    final class UidState {
-        public final int uid;
-
-        public ArrayMap<String, Ops> pkgOps;
-
-        // true indicates there is an interested observer, false there isn't but it has such an op
-        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
-        public SparseBooleanArray foregroundOps;
-        public boolean hasForegroundWatchers;
-
-        public UidState(int uid) {
-            this.uid = uid;
-        }
-
-        public void clear() {
-            mAppOpsServiceInterface.removeUid(uid);
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-                }
-            }
-            pkgOps = null;
-        }
-
-        public boolean isDefault() {
-            boolean areAllPackageModesDefault = true;
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
-                            UserHandle.getUserId(uid))) {
-                        areAllPackageModesDefault = false;
-                        break;
-                    }
-                }
-            }
-            return (pkgOps == null || pkgOps.isEmpty())
-                    && mAppOpsServiceInterface.areUidModesDefault(uid)
-                    && areAllPackageModesDefault;
-        }
-
-        // Functions for uid mode access and manipulation.
-        public SparseIntArray getNonDefaultUidModes() {
-            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
-        }
-
-        public int getUidMode(int op) {
-            return mAppOpsServiceInterface.getUidMode(uid, op);
-        }
-
-        public boolean setUidMode(int op, int mode) {
-            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        int evalMode(int op, int mode) {
-            return getUidStateTracker().evalMode(uid, op, mode);
-        }
-
-        public void evalForegroundOps() {
-            foregroundOps = null;
-            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
-            if (pkgOps != null) {
-                for (int i = pkgOps.size() - 1; i >= 0; i--) {
-                    foregroundOps = mAppOpsServiceInterface
-                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
-                                    UserHandle.getUserId(uid));
-                }
-            }
-            hasForegroundWatchers = false;
-            if (foregroundOps != null) {
-                for (int i = 0;  i < foregroundOps.size(); i++) {
-                    if (foregroundOps.valueAt(i)) {
-                        hasForegroundWatchers = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public int getState() {
-            return getUidStateTracker().getUidState(uid);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public void dump(PrintWriter pw, long nowElapsed) {
-            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
-        }
-    }
-
-    final static class Ops extends SparseArray<Op> {
-        final String packageName;
-        final UidState uidState;
-
-        /**
-         * The restriction properties of the package. If {@code null} it could not have been read
-         * yet and has to be refreshed.
-         */
-        @Nullable RestrictionBypass bypass;
-
-        /** Lazily populated cache of attributionTags of this package */
-        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
-        /**
-         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
-         * than or equal to {@link #knownAttributionTags}.
-         */
-        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
-        Ops(String _packageName, UidState _uidState) {
-            packageName = _packageName;
-            uidState = _uidState;
-        }
-    }
-
-    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
-    private static final class PackageVerificationResult {
-
-        final RestrictionBypass bypass;
-        final boolean isAttributionTagValid;
-
-        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
-            this.bypass = bypass;
-            this.isAttributionTagValid = isAttributionTagValid;
-        }
-    }
-
-    final class Op {
-        int op;
-        int uid;
-        final UidState uidState;
-        final @NonNull String packageName;
-
-        /** attributionTag -> AttributedOp */
-        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
-        Op(UidState uidState, String packageName, int op, int uid) {
-            this.op = op;
-            this.uid = uid;
-            this.uidState = uidState;
-            this.packageName = packageName;
-        }
-
-        @Mode int getMode() {
-            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
-                    UserHandle.getUserId(this.uid));
-        }
-        void setMode(@Mode int mode) {
-            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
-                    UserHandle.getUserId(this.uid));
-        }
-
-        void removeAttributionsWithNoTime() {
-            for (int i = mAttributions.size() - 1; i >= 0; i--) {
-                if (!mAttributions.valueAt(i).hasAnyTime()) {
-                    mAttributions.removeAt(i);
-                }
-            }
-        }
-
-        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
-                @Nullable String attributionTag) {
-            AttributedOp attributedOp;
-
-            attributedOp = mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
-                mAttributions.put(attributionTag, attributedOp);
-            }
-
-            return attributedOp;
-        }
-
-        @NonNull OpEntry createEntryLocked() {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
-                    new ArrayMap<>(numAttributions);
-            for (int i = 0; i < numAttributions; i++) {
-                attributionEntries.put(mAttributions.keyAt(i),
-                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
-            for (int i = 0; i < numAttributions; i++) {
-                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
-                    attributionEntries.put(mAttributions.keyAt(i),
-                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
-                    break;
-                }
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        boolean isRunning() {
-            final int numAttributions = mAttributions.size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (mAttributions.valueAt(i).isRunning()) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-    }
-
-    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
     final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
-    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
-        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
-        public static final int ALL_OPS = -2;
-
-        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
-        // Otherwise we can just use the IBinder object.
-        private final IAppOpsCallback mCallback;
-
-        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
-                int callingUid, int callingPid) {
-            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
-            this.mCallback = callback;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ModeCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, getWatchingUid());
-            sb.append(" flags=0x");
-            sb.append(Integer.toHexString(getFlags()));
-            switch (getWatchedOpCode()) {
-                case OP_NONE:
-                    break;
-                case ALL_OPS:
-                    sb.append(" op=(all)");
-                    break;
-                default:
-                    sb.append(" op=");
-                    sb.append(opToName(getWatchedOpCode()));
-                    break;
-            }
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, getCallingUid());
-            sb.append(" pid=");
-            sb.append(getCallingPid());
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void unlinkToDeath() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingMode(mCallback);
-        }
-
-        @Override
-        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
-            mCallback.opChanged(op, uid, packageName);
-        }
-    }
-
-    final class ActiveCallback implements DeathRecipient {
-        final IAppOpsActiveCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ActiveCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingActive(mCallback);
-        }
-    }
-
-    final class StartedCallback implements DeathRecipient {
-        final IAppOpsStartedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("StartedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingStarted(mCallback);
-        }
-    }
-
-    final class NotedCallback implements DeathRecipient {
-        final IAppOpsNotedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("NotedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingNoted(mCallback);
-        }
-    }
-
-    /**
-     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
-     */
-    static void onClientDeath(@NonNull AttributedOp attributedOp,
-            @NonNull IBinder clientId) {
-        attributedOp.onClientDeath(clientId);
-    }
-
-
     /**
      * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
      * so that we do not log the same operation twice between instances
@@ -925,20 +233,12 @@
     }
 
     public AppOpsService(File storagePath, Handler handler, Context context) {
-        mContext = context;
+        this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
+    }
 
-        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
-            int switchCode = AppOpsManager.opToSwitch(switchedCode);
-            mSwitchedOps.put(switchCode,
-                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
-        }
-        mAppOpsServiceInterface =
-                new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
-                mAppOpsServiceInterface);
-
-        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
-        mFile = new AtomicFile(storagePath, "appops");
+    @VisibleForTesting
+    public AppOpsService(Handler handler, Context context,
+            AppOpsServiceInterface appOpsServiceInterface) {
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
             mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
                     "noteOpStackTraces.json");
@@ -946,185 +246,25 @@
         } else {
             mNoteOpCallerStacktracesFile = null;
         }
+
+        mAppOpsService = appOpsServiceInterface;
+        mContext = context;
         mHandler = handler;
-        mConstants = new Constants(mHandler);
-        readState();
     }
 
+    /**
+     * Publishes binder and local service.
+     */
     public void publish() {
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
         LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
-    /** Handler for work when packages are removed or updated */
-    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
-            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
-            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
-                synchronized (AppOpsService.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
-                    Ops removedOps = uidState.pkgOps.remove(pkgName);
-                    if (removedOps != null) {
-                        scheduleFastWriteLocked();
-                    }
-                }
-            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
-                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
-                if (pkg == null) {
-                    return;
-                }
-
-                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
-                ArraySet<String> attributionTags = new ArraySet<>();
-                attributionTags.add(null);
-                if (pkg.getAttributions() != null) {
-                    int numAttributions = pkg.getAttributions().size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
-                        attributionTags.add(attribution.getTag());
-
-                        int numInheritFrom = attribution.getInheritFrom().size();
-                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
-                                inheritFromNum++) {
-                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
-                                    attribution.getTag());
-                        }
-                    }
-                }
-
-                synchronized (AppOpsService.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-
-                    Ops ops = uidState.pkgOps.get(pkgName);
-                    if (ops == null) {
-                        return;
-                    }
-
-                    // Reset cached package properties to re-initialize when needed
-                    ops.bypass = null;
-                    ops.knownAttributionTags.clear();
-
-                    // Merge data collected for removed attributions into their successor
-                    // attributions
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
-                                attributionNum--) {
-                            String attributionTag = op.mAttributions.keyAt(attributionNum);
-
-                            if (attributionTags.contains(attributionTag)) {
-                                // attribution still exist after upgrade
-                                continue;
-                            }
-
-                            String newAttributionTag = dstAttributionTags.get(attributionTag);
-
-                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
-                                    newAttributionTag);
-                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
-                            op.mAttributions.removeAt(attributionNum);
-
-                            scheduleFastWriteLocked();
-                        }
-                    }
-                }
-            }
-        }
-    };
-
+    /**
+     * Finishes boot sequence.
+     */
     public void systemReady() {
-        mConstants.startMonitoring(mContext.getContentResolver());
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
-        IntentFilter packageUpdateFilter = new IntentFilter();
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        packageUpdateFilter.addDataScheme("package");
-
-        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
-                packageUpdateFilter, null, null);
-
-        synchronized (this) {
-            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
-                int uid = mUidStates.keyAt(uidNum);
-                UidState uidState = mUidStates.valueAt(uidNum);
-
-                String[] pkgsInUid = getPackagesForUid(uidState.uid);
-                if (ArrayUtils.isEmpty(pkgsInUid)) {
-                    uidState.clear();
-                    mUidStates.removeAt(uidNum);
-                    scheduleFastWriteLocked();
-                    continue;
-                }
-
-                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
-                if (pkgs == null) {
-                    continue;
-                }
-
-                int numPkgs = pkgs.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    String pkg = pkgs.keyAt(pkgNum);
-
-                    String action;
-                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
-                        action = Intent.ACTION_PACKAGE_REMOVED;
-                    } else {
-                        action = Intent.ACTION_PACKAGE_REPLACED;
-                    }
-
-                    SystemServerInitThreadPool.submit(
-                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
-                                    .setData(Uri.fromParts("package", pkg, null))
-                                    .putExtra(Intent.EXTRA_UID, uid)),
-                            "Update app-ops uidState in case package " + pkg + " changed");
-                }
-            }
-        }
-
-        final IntentFilter packageSuspendFilter = new IntentFilter();
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        mContext.registerReceiverAsUser(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
-                final String[] changedPkgs = intent.getStringArrayExtra(
-                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
-                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
-                    synchronized (AppOpsService.this) {
-                        onModeChangedListeners =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (onModeChangedListeners == null) {
-                            continue;
-                        }
-                    }
-                    for (int i = 0; i < changedUids.length; i++) {
-                        final int changedUid = changedUids[i];
-                        final String changedPkg = changedPkgs[i];
-                        // We trust packagemanager to insert matching uid and packageNames in the
-                        // extras
-                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
-                    }
-                }
-            }
-        }, UserHandle.ALL, packageSuspendFilter, null, null);
+        mAppOpsService.systemReady();
 
         final IntentFilter packageAddedFilter = new IntentFilter();
         packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -1132,9 +272,8 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                final Uri data = intent.getData();
 
-                final String packageName = data.getSchemeSpecificPart();
+                final String packageName = intent.getData().getSchemeSpecificPart();
                 PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
                         PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
                 if (isSamplingTarget(pi)) {
@@ -1169,8 +308,6 @@
                         }
                     }
                 });
-
-        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     /**
@@ -1185,132 +322,18 @@
         mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
     }
 
+    /**
+     * Notify when a package is removed
+     */
     public void packageRemoved(int uid, String packageName) {
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null) {
-                return;
-            }
-
-            Ops removedOps = null;
-
-            // Remove any package state if such.
-            if (uidState.pkgOps != null) {
-                removedOps = uidState.pkgOps.remove(packageName);
-                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            }
-
-            // If we just nuked the last package state check if the UID is valid.
-            if (removedOps != null && uidState.pkgOps.isEmpty()
-                    && getPackagesForUid(uid).length <= 0) {
-                uidState.clear();
-                mUidStates.remove(uid);
-            }
-
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-
-                final int numOps = removedOps.size();
-                for (int opNum = 0; opNum < numOps; opNum++) {
-                    final Op op = removedOps.valueAt(opNum);
-
-                    final int numAttributions = op.mAttributions.size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
-                        while (attributedOp.isRunning()) {
-                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
-                        }
-                        while (attributedOp.isPaused()) {
-                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
-                        }
-                    }
-                }
-            }
-        }
-
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
-                    mHistoricalRegistry, uid, packageName));
+        mAppOpsService.packageRemoved(uid, packageName);
     }
 
+    /**
+     * Notify when a uid is removed.
+     */
     public void uidRemoved(int uid) {
-        synchronized (this) {
-            if (mUidStates.indexOfKey(uid) >= 0) {
-                mUidStates.get(uid).clear();
-                mUidStates.remove(uid);
-                scheduleFastWriteLocked();
-            }
-        }
-    }
-
-    // The callback method from ForegroundPolicyInterface
-    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, true);
-
-            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
-                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
-                    if (!uidState.foregroundOps.valueAt(fgi)) {
-                        continue;
-                    }
-                    final int code = uidState.foregroundOps.keyAt(fgi);
-
-                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
-                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
-                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                AppOpsService::notifyOpChangedForAllPkgsInUid,
-                                this, code, uidState.uid, true, null));
-                    } else if (uidState.pkgOps != null) {
-                        final ArraySet<OnOpModeChangedListener> listenerSet =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (listenerSet != null) {
-                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
-                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
-                                if ((listener.getFlags()
-                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
-                                        || !listener.isWatchingUid(uidState.uid)) {
-                                    continue;
-                                }
-                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
-                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
-                                    if (op == null) {
-                                        continue;
-                                    }
-                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
-                                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                                AppOpsService::notifyOpChanged,
-                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
-                                                uidState.pkgOps.keyAt(pkgi)));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (uidState != null && uidState.pkgOps != null) {
-                int numPkgs = uidState.pkgOps.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = 0; attributionNum < numAttributions;
-                                attributionNum++) {
-                            AttributedOp attributedOp = op.mAttributions.valueAt(
-                                    attributionNum);
-
-                            attributedOp.onUidStateChanged(state);
-                        }
-                    }
-                }
-            }
-        }
+        mAppOpsService.uidRemoved(uid);
     }
 
     /**
@@ -1318,542 +341,60 @@
      */
     public void updateUidProcState(int uid, int procState,
             @ActivityManager.ProcessCapability int capability) {
-        synchronized (this) {
-            getUidStateTracker().updateUidProcState(uid, procState, capability);
-            if (!mUidStates.contains(uid)) {
-                UidState uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-                onUidStateChanged(uid,
-                        AppOpsUidStateTracker.processStateToUidState(procState), false);
-            }
-        }
+        mAppOpsService.updateUidProcState(uid, procState, capability);
     }
 
+    /**
+     * Initiates shutdown.
+     */
     public void shutdown() {
-        Slog.w(TAG, "Writing app ops before shutdown...");
-        boolean doWrite = false;
-        synchronized (this) {
-            if (mWriteScheduled) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                mHandler.removeCallbacks(mWriteRunner);
-                doWrite = true;
-            }
-        }
-        if (doWrite) {
-            writeState();
-        }
+        mAppOpsService.shutdown();
+
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
             writeNoteOps();
         }
-
-        mHistoricalRegistry.shutdown();
-    }
-
-    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int j=0; j<pkgOps.size(); j++) {
-                Op curOp = pkgOps.valueAt(j);
-                resOps.add(getOpEntryForResult(curOp));
-            }
-        } else {
-            for (int j=0; j<ops.length; j++) {
-                Op curOp = pkgOps.get(ops[j]);
-                if (curOp != null) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(getOpEntryForResult(curOp));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    @Nullable
-    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
-            @Nullable int[] ops) {
-        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-        if (opModes == null) {
-            return null;
-        }
-
-        int opModeCount = opModes.size();
-        if (opModeCount == 0) {
-            return null;
-        }
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int i = 0; i < opModeCount; i++) {
-                int code = opModes.keyAt(i);
-                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-            }
-        } else {
-            for (int j=0; j<ops.length; j++) {
-                int code = ops[j];
-                if (opModes.indexOfKey(code) >= 0) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
-        return op.createEntryLocked();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
-        final int callingUid = Binder.getCallingUid();
-        final boolean hasAllPackageAccess = mContext.checkPermission(
-                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
-                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
-        ArrayList<AppOpsManager.PackageOps> res = null;
-        synchronized (this) {
-            final int uidStateCount = mUidStates.size();
-            for (int i = 0; i < uidStateCount; i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
-                    continue;
-                }
-                ArrayMap<String, Ops> packages = uidState.pkgOps;
-                final int packageCount = packages.size();
-                for (int j = 0; j < packageCount; j++) {
-                    Ops pkgOps = packages.valueAt(j);
-                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-                    if (resOps != null) {
-                        if (res == null) {
-                            res = new ArrayList<>();
-                        }
-                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
-                        // Caller can always see their packages and with a permission all.
-                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
-                            res.add(resPackage);
-                        }
-                    }
-                }
-            }
-        }
-        return res;
+        return mAppOpsService.getPackagesForOps(ops);
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
             int[] ops) {
-        enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return Collections.emptyList();
-        }
-        synchronized (this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
-                    /* edit */ false);
-            if (pkgOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        // We get to access everything
-        if (callingUid == Process.myPid()) {
-            return;
-        }
-        // Apps can access their own data
-        if (uid == callingUid && packageName != null
-                && checkPackage(uid, packageName) == MODE_ALLOWED) {
-            return;
-        }
-        // Otherwise, you need a permission...
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), callingUid, null);
-    }
-
-    /**
-     * Verify that historical appop request arguments are valid.
-     */
-    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
-            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags) {
-        if ((filter & FILTER_BY_UID) != 0) {
-            Preconditions.checkArgument(uid != Process.INVALID_UID);
-        } else {
-            Preconditions.checkArgument(uid == Process.INVALID_UID);
-        }
-
-        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
-            Objects.requireNonNull(packageName);
-        } else {
-            Preconditions.checkArgument(packageName == null);
-        }
-
-        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
-            Preconditions.checkArgument(attributionTag == null);
-        }
-
-        if ((filter & FILTER_BY_OP_NAMES) != 0) {
-            Objects.requireNonNull(opNames);
-        } else {
-            Preconditions.checkArgument(opNames == null);
-        }
-
-        Preconditions.checkFlagsArgument(filter,
-                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
-                        | FILTER_BY_OP_NAMES);
-        Preconditions.checkArgumentNonnegative(beginTimeMillis);
-        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
-        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+        return mAppOpsService.getOpsForPackage(uid, packageName, ops);
     }
 
     @Override
     public void getHistoricalOps(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        PackageManager pm = mContext.getPackageManager();
-
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
-        if (!isSelfRequest) {
-            boolean isCallerInstrumented =
-                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
-            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
-            boolean isCallerPermissionController;
-            try {
-                isCallerPermissionController = pm.getPackageUidAsUser(
-                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
-                        UserHandle.getUserId(Binder.getCallingUid()))
-                        == Binder.getCallingUid();
-            } catch (PackageManager.NameNotFoundException doesNotHappen) {
-                return;
-            }
-
-            boolean doesCallerHavePermission = mContext.checkPermission(
-                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid())
-                    == PackageManager.PERMISSION_GRANTED;
-
-            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
-                    && !doesCallerHavePermission) {
-                mHandler.post(() -> callback.sendResult(new Bundle()));
-                return;
-            }
-
-            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-        }
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                        new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
+        mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
+                dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
     public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
+        mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
+                opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
     public void reloadNonHistoricalState() {
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
-        writeState();
-        readState();
+        mAppOpsService.reloadNonHistoricalState();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    null, uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void pruneOpLocked(Op op, int uid, String packageName) {
-        op.removeAttributionsWithNoTime();
-
-        if (op.mAttributions.isEmpty()) {
-            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
-            if (ops != null) {
-                ops.remove(op.op);
-                op.setMode(AppOpsManager.opToDefaultMode(op.op));
-                if (ops.size() <= 0) {
-                    UidState uidState = ops.uidState;
-                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-                    if (pkgOps != null) {
-                        pkgOps.remove(ops.packageName);
-                        mAppOpsServiceInterface.removePackage(ops.packageName,
-                                UserHandle.getUserId(uidState.uid));
-                        if (pkgOps.isEmpty()) {
-                            uidState.pkgOps = null;
-                        }
-                        if (uidState.isDefault()) {
-                            uidState.clear();
-                            mUidStates.remove(uid);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
-        if (callingPid == Process.myPid()) {
-            return;
-        }
-        final int callingUser = UserHandle.getUserId(callingUid);
-        synchronized (this) {
-            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
-                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
-                    // Profile owners are allowed to change modes but only for apps
-                    // within their user.
-                    return;
-                }
-            }
-        }
-        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        return mAppOpsService.getUidOps(uid, ops);
     }
 
     @Override
     public void setUidMode(int code, int uid, int mode) {
-        setUidMode(code, uid, mode, null);
-    }
-
-    private void setUidMode(int code, int uid, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        if (DEBUG) {
-            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
-                    + " by uid " + Binder.getCallingUid());
-        }
-
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        code = AppOpsManager.opToSwitch(code);
-
-        if (permissionPolicyCallback == null) {
-            updatePermissionRevokedCompat(uid, code, mode);
-        }
-
-        int previousMode;
-        synchronized (this) {
-            final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                if (mode == defaultMode) {
-                    return;
-                }
-                uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-            }
-            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                previousMode = uidState.getUidMode(code);
-            } else {
-                // doesn't look right but is legacy behavior.
-                previousMode = MODE_DEFAULT;
-            }
-
-            if (!uidState.setUidMode(code, mode)) {
-                return;
-            }
-            uidState.evalForegroundOps();
-            if (mode != MODE_ERRORED && mode != previousMode) {
-                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-            }
-        }
-
-        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
-        notifyOpChangedSync(code, uid, null, mode, previousMode);
-    }
-
-    /**
-     * Notify that an op changed for all packages in an uid.
-     *
-     * @param code The op that changed
-     * @param uid The uid the op was changed for
-     * @param onlyForeground Only notify watchers that watch for foreground changes
-     */
-    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable IAppOpsCallback callbackToIgnore) {
-        ModeCallback listenerToIgnore = callbackToIgnore != null
-                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
-        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
-                listenerToIgnore);
-    }
-
-    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
-        PackageManager packageManager = mContext.getPackageManager();
-        if (packageManager == null) {
-            // This can only happen during early boot. At this time the permission state and appop
-            // state are in sync
-            return;
-        }
-
-        String[] packageNames = packageManager.getPackagesForUid(uid);
-        if (ArrayUtils.isEmpty(packageNames)) {
-            return;
-        }
-        String packageName = packageNames[0];
-
-        int[] ops = mSwitchedOps.get(switchCode);
-        for (int code : ops) {
-            String permissionName = AppOpsManager.opToPermission(code);
-            if (permissionName == null) {
-                continue;
-            }
-
-            if (packageManager.checkPermission(permissionName, packageName)
-                    != PackageManager.PERMISSION_GRANTED) {
-                continue;
-            }
-
-            PermissionInfo permissionInfo;
-            try {
-                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                e.printStackTrace();
-                continue;
-            }
-
-            if (!permissionInfo.isRuntime()) {
-                continue;
-            }
-
-            boolean supportsRuntimePermissions = getPackageManagerInternal()
-                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
-            UserHandle user = UserHandle.getUserHandleForUid(uid);
-            boolean isRevokedCompat;
-            if (permissionInfo.backgroundPermission != null) {
-                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
-                        == PackageManager.PERMISSION_GRANTED) {
-                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
-                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
-                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                                + " permission state, this is discouraged and you should revoke the"
-                                + " runtime permission instead: uid=" + uid + ", switchCode="
-                                + switchCode + ", mode=" + mode + ", permission="
-                                + permissionInfo.backgroundPermission);
-                    }
-
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
-                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                                isBackgroundRevokedCompat
-                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-                }
-
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
-                        && mode != AppOpsManager.MODE_FOREGROUND;
-            } else {
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-            }
-
-            if (isRevokedCompat && supportsRuntimePermissions) {
-                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                        + " permission state, this is discouraged and you should revoke the"
-                        + " runtime permission instead: uid=" + uid + ", switchCode="
-                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
-            }
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                packageManager.updatePermissionFlags(permissionName, packageName,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
-                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
-            int previousMode) {
-        final StorageManagerInternal storageManagerInternal =
-                LocalServices.getService(StorageManagerInternal.class);
-        if (storageManagerInternal != null) {
-            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
-        }
+        mAppOpsService.setUidMode(code, uid, mode, null);
     }
 
     /**
@@ -1866,309 +407,12 @@
      */
     @Override
     public void setMode(int code, int uid, @NonNull String packageName, int mode) {
-        setMode(code, uid, packageName, mode, null);
-    }
-
-    private void setMode(int code, int uid, @NonNull String packageName, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        ArraySet<OnOpModeChangedListener> repCbs = null;
-        code = AppOpsManager.opToSwitch(code);
-
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot setMode", e);
-            return;
-        }
-
-        int previousMode = MODE_DEFAULT;
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
-            if (op != null) {
-                if (op.getMode() != mode) {
-                    previousMode = op.getMode();
-                    op.setMode(mode);
-
-                    if (uidState != null) {
-                        uidState.evalForegroundOps();
-                    }
-                    ArraySet<OnOpModeChangedListener> cbs =
-                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    if (repCbs != null && permissionPolicyCallback != null) {
-                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
-                    }
-                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
-                        // If going into the default mode, prune this op
-                        // if there is nothing else interesting in it.
-                        pruneOpLocked(op, uid, packageName);
-                    }
-                    scheduleFastWriteLocked();
-                    if (mode != MODE_ERRORED) {
-                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-                    }
-                }
-            }
-        }
-        if (repCbs != null) {
-            mHandler.sendMessage(PooledLambda.obtainMessage(
-                    AppOpsService::notifyOpChanged,
-                    this, repCbs, code, uid, packageName));
-        }
-
-        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
-    }
-
-    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
-            int uid, String packageName) {
-        for (int i = 0; i < callbacks.size(); i++) {
-            final OnOpModeChangedListener callback = callbacks.valueAt(i);
-            notifyOpChanged(callback, code, uid, packageName);
-        }
-    }
-
-    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
-            int uid, String packageName) {
-        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
-    }
-
-    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
-            int op, int uid, String packageName, int previousMode) {
-        boolean duplicate = false;
-        if (reports == null) {
-            reports = new ArrayList<>();
-        } else {
-            final int reportCount = reports.size();
-            for (int j = 0; j < reportCount; j++) {
-                ChangeRec report = reports.get(j);
-                if (report.op == op && report.pkg.equals(packageName)) {
-                    duplicate = true;
-                    break;
-                }
-            }
-        }
-        if (!duplicate) {
-            reports.add(new ChangeRec(op, uid, packageName, previousMode));
-        }
-
-        return reports;
-    }
-
-    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
-            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
-            int op, int uid, String packageName, int previousMode,
-            ArraySet<OnOpModeChangedListener> cbs) {
-        if (cbs == null) {
-            return callbacks;
-        }
-        if (callbacks == null) {
-            callbacks = new HashMap<>();
-        }
-        final int N = cbs.size();
-        for (int i=0; i<N; i++) {
-            OnOpModeChangedListener cb = cbs.valueAt(i);
-            ArrayList<ChangeRec> reports = callbacks.get(cb);
-            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
-            if (changed != reports) {
-                callbacks.put(cb, changed);
-            }
-        }
-        return callbacks;
-    }
-
-    static final class ChangeRec {
-        final int op;
-        final int uid;
-        final String pkg;
-        final int previous_mode;
-
-        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
-            op = _op;
-            uid = _uid;
-            pkg = _pkg;
-            previous_mode = _previous_mode;
-        }
+        mAppOpsService.setMode(code, uid, packageName, mode, null);
     }
 
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
-        final int callingPid = Binder.getCallingPid();
-        final int callingUid = Binder.getCallingUid();
-        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
-                true, true, "resetAllModes", null);
-
-        int reqUid = -1;
-        if (reqPackageName != null) {
-            try {
-                reqUid = AppGlobals.getPackageManager().getPackageUid(
-                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-
-        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
-        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
-        ArrayList<ChangeRec> allChanges = new ArrayList<>();
-        synchronized (this) {
-            boolean changed = false;
-            for (int i = mUidStates.size() - 1; i >= 0; i--) {
-                UidState uidState = mUidStates.valueAt(i);
-
-                SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
-                    final int uidOpCount = opModes.size();
-                    for (int j = uidOpCount - 1; j >= 0; j--) {
-                        final int code = opModes.keyAt(j);
-                        if (AppOpsManager.opAllowsReset(code)) {
-                            int previousMode = opModes.valueAt(j);
-                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
-                            for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode,
-                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
-                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode, mAppOpsServiceInterface
-                                                .getPackageModeChangedListeners(packageName));
-
-                                allChanges = addChange(allChanges, code, uidState.uid,
-                                        packageName, previousMode);
-                            }
-                        }
-                    }
-                }
-
-                if (uidState.pkgOps == null) {
-                    continue;
-                }
-
-                if (reqUserId != UserHandle.USER_ALL
-                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
-                    // Skip any ops for a different user
-                    continue;
-                }
-
-                Map<String, Ops> packages = uidState.pkgOps;
-                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
-                boolean uidChanged = false;
-                while (it.hasNext()) {
-                    Map.Entry<String, Ops> ent = it.next();
-                    String packageName = ent.getKey();
-                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
-                        // Skip any ops for a different package
-                        continue;
-                    }
-                    Ops pkgOps = ent.getValue();
-                    for (int j=pkgOps.size()-1; j>=0; j--) {
-                        Op curOp = pkgOps.valueAt(j);
-                        if (shouldDeferResetOpToDpm(curOp.op)) {
-                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
-                            continue;
-                        }
-                        if (AppOpsManager.opAllowsReset(curOp.op)
-                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
-                            int previousMode = curOp.getMode();
-                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
-                            changed = true;
-                            uidChanged = true;
-                            final int uid = curOp.uidState.uid;
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode,
-                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mAppOpsServiceInterface
-                                            .getPackageModeChangedListeners(packageName));
-
-                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
-                                    previousMode);
-                            curOp.removeAttributionsWithNoTime();
-                            if (curOp.mAttributions.isEmpty()) {
-                                pkgOps.removeAt(j);
-                            }
-                        }
-                    }
-                    if (pkgOps.size() == 0) {
-                        it.remove();
-                        mAppOpsServiceInterface.removePackage(packageName,
-                                UserHandle.getUserId(uidState.uid));
-                    }
-                }
-                if (uidState.isDefault()) {
-                    uidState.clear();
-                    mUidStates.remove(uidState.uid);
-                }
-                if (uidChanged) {
-                    uidState.evalForegroundOps();
-                }
-            }
-
-            if (changed) {
-                scheduleFastWriteLocked();
-            }
-        }
-        if (callbacks != null) {
-            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
-                    : callbacks.entrySet()) {
-                OnOpModeChangedListener cb = ent.getKey();
-                ArrayList<ChangeRec> reports = ent.getValue();
-                for (int i=0; i<reports.size(); i++) {
-                    ChangeRec rep = reports.get(i);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyOpChanged,
-                            this, cb, rep.op, rep.uid, rep.pkg));
-                }
-            }
-        }
-
-        int numChanges = allChanges.size();
-        for (int i = 0; i < numChanges; i++) {
-            ChangeRec change = allChanges.get(i);
-            notifyOpChangedSync(change.op, change.uid, change.pkg,
-                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
-        }
-    }
-
-    private boolean shouldDeferResetOpToDpm(int op) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        return dpmi != null && dpmi.supportsResetOp(op);
-    }
-
-    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
-    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        dpmi.resetOp(op, packageName, userId);
-    }
-
-    private void evalAllForegroundOpsLocked() {
-        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
-            final UidState uidState = mUidStates.valueAt(uidi);
-            if (uidState.foregroundOps != null) {
-                uidState.evalForegroundOps();
-            }
-        }
+        mAppOpsService.resetAllModes(reqUserId, reqPackageName);
     }
 
     @Override
@@ -2179,66 +423,17 @@
     @Override
     public void startWatchingModeWithFlags(int op, String packageName, int flags,
             IAppOpsCallback callback) {
-        int watchedUid = -1;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        // TODO: should have a privileged permission to protect this.
-        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
-        // the USAGE_STATS permission since this can provide information about when an
-        // app is in the foreground?
-        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
-                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
-        if (callback == null) {
-            return;
-        }
-        final boolean mayWatchPackageName = packageName != null
-                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
-        synchronized (this) {
-            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
-            int notifiedOps;
-            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
-                if (op == OP_NONE) {
-                    notifiedOps = ALL_OPS;
-                } else {
-                    notifiedOps = op;
-                }
-            } else {
-                notifiedOps = switchOp;
-            }
-
-            ModeCallback cb = mModeWatchers.get(callback.asBinder());
-            if (cb == null) {
-                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
-                        callingPid);
-                mModeWatchers.put(callback.asBinder(), cb);
-            }
-            if (switchOp != AppOpsManager.OP_NONE) {
-                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
-            }
-            if (mayWatchPackageName) {
-                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
-            }
-            evalAllForegroundOpsLocked();
-        }
+        mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
     }
 
     @Override
     public void stopWatchingMode(IAppOpsCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
-            if (cb != null) {
-                cb.unlinkToDeath();
-                mAppOpsServiceInterface.removeListener(cb);
-            }
-
-            evalAllForegroundOpsLocked();
-        }
+        mAppOpsService.stopWatchingMode(callback);
     }
 
+    /**
+     * @return the current {@link CheckOpsDelegate}.
+     */
     public CheckOpsDelegate getAppOpsServiceDelegate() {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -2246,6 +441,9 @@
         }
     }
 
+    /**
+     * Sets the appops {@link CheckOpsDelegate}
+     */
     public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -2269,58 +467,7 @@
 
     private int checkOperationImpl(int code, int uid, String packageName,
             @Nullable String attributionTag, boolean raw) {
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
-    }
-
-    /**
-     * Get the mode of an app-op.
-     *
-     * @param code The code of the op
-     * @param uid The uid of the package the op belongs to
-     * @param packageName The package the op belongs to
-     * @param raw If the raw state of eval-ed state should be checked.
-     *
-     * @return The mode of the op
-     */
-    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean raw) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "checkOperation", e);
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        synchronized (this) {
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
-                return AppOpsManager.MODE_IGNORED;
-            }
-            code = AppOpsManager.opToSwitch(code);
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState != null
-                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                final int rawMode = uidState.getUidMode(code);
-                return raw ? rawMode : uidState.evalMode(code, rawMode);
-            }
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
-            if (op == null) {
-                return AppOpsManager.opToDefaultMode(code);
-            }
-            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
-        }
+        return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
     }
 
     @Override
@@ -2340,7 +487,8 @@
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+                Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
 
@@ -2348,58 +496,35 @@
                 code, usage, uid, mode, exceptionPackages);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
+                UID_ANY));
     }
 
 
     @Override
     public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+                Binder.getCallingUid(), -1);
 
         mAudioRestrictionManager.setCameraAudioRestriction(mode);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
                 AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
                 AppOpsManager.OP_VIBRATE, UID_ANY));
     }
 
     @Override
     public int checkPackage(int uid, String packageName) {
-        Objects.requireNonNull(packageName);
-        try {
-            verifyAndGetBypass(uid, packageName, null);
-            // When the caller is the system, it's possible that the packageName is the special
-            // one (e.g., "root") which isn't actually existed.
-            if (resolveUid(packageName) == uid
-                    || (isPackageExisted(packageName)
-                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
-                return AppOpsManager.MODE_ALLOWED;
-            }
-            return AppOpsManager.MODE_ERRORED;
-        } catch (SecurityException ignored) {
-            return AppOpsManager.MODE_ERRORED;
-        }
+        return mAppOpsService.checkPackage(uid, packageName);
     }
 
     private boolean isPackageExisted(String packageName) {
         return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
     }
 
-    /**
-     * This method will check with PackageManager to determine if the package provided should
-     * be visible to the {@link Binder#getCallingUid()}.
-     *
-     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
-     */
-    private boolean filterAppAccessUnlocked(String packageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        return LocalServices.getService(PackageManagerInternal.class)
-                .filterAppAccess(packageName, callingUid, userId);
-    }
-
     @Override
     public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -2445,13 +570,20 @@
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
+            final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
                     resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
-            if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
+                    proxyFlags);
+            if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
                         proxiedPackageName);
             }
+            if (shouldCollectAsyncNotedOp) {
+                boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+                        resolveProxyPackageName, proxyAttributionTag, null);
+                collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+                        message, shouldCollectMessage);
+            }
         }
 
         String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -2463,9 +595,32 @@
 
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
-        return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
-                proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
-                proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+        final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
+                resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
+                proxyAttributionTag, proxiedFlags);
+
+        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+                resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
+        if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
+            collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
+                    isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
+                    message, shouldCollectMessage);
+        }
+
+
+        return new SyncNotedAppOp(result, code,
+                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                resolveProxiedPackageName);
+    }
+
+    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+        if (attributionSource.getUid() != Binder.getCallingUid()
+                && attributionSource.isTrusted(mContext)) {
+            return true;
+        }
+        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     @Override
@@ -2479,258 +634,58 @@
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
             @Nullable String message, boolean shouldCollectMessage) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
+        int result = mAppOpsService.noteOperation(code, uid, packageName,
+                attributionTag, message);
+
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                    packageName);
-        }
-        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
-                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
-    }
 
-    private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            boolean wasNull = attributionTag == null;
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "noteOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                    packageName);
+        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+                    resolvedPackageName, attributionTag, null);
+
+        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+            collectAsyncNotedOp(uid, resolvedPackageName, code,
+                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
         }
 
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName + "flags: " +
-                        AppOpsManager.flagsToString(flags));
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            if (attributedOp.isRunning()) {
-                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
-                        + code + " startTime of in progress event="
-                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
-            }
-
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            final UidState uidState = ops.uidState;
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
-                attributedOp.rejected(uidState.getState(), flags);
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                        packageName);
-            }
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (uidMode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            uidMode);
-                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            mode);
-                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "noteOperation: allowing code " + code + " uid " + uid + " package "
-                                + packageName + (attributionTag == null ? ""
-                                : "." + attributionTag) + " flags: "
-                                + AppOpsManager.flagsToString(flags));
-            }
-            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                    AppOpsManager.MODE_ALLOWED);
-            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState.getState(),
-                    flags);
-
-            if (shouldCollectAsyncNotedOp) {
-                collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
-                        shouldCollectMessage);
-            }
-
-            return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
-                    packageName);
-        }
+        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+                resolvedPackageName);
     }
 
     // TODO moltmann: Allow watching for attribution ops
     @Override
     public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        if (ops != null) {
-            Preconditions.checkArrayElementsInRange(ops, 0,
-                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
-        }
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mActiveWatchers.put(callback.asBinder(), callbacks);
-            }
-            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, activeCallback);
-            }
-        }
+        mAppOpsService.startWatchingActive(ops, callback);
     }
 
     @Override
     public void stopWatchingActive(IAppOpsActiveCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            final SparseArray<ActiveCallback> activeCallbacks =
-                    mActiveWatchers.remove(callback.asBinder());
-            if (activeCallbacks == null) {
-                return;
-            }
-            final int callbackCount = activeCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                activeCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingActive(callback);
     }
 
     @Override
     public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mStartedWatchers.put(callback.asBinder(), callbacks);
-            }
-
-            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, startedCallback);
-            }
-        }
+        mAppOpsService.startWatchingStarted(ops, callback);
     }
 
     @Override
     public void stopWatchingStarted(IAppOpsStartedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            final SparseArray<StartedCallback> startedCallbacks =
-                    mStartedWatchers.remove(callback.asBinder());
-            if (startedCallbacks == null) {
-                return;
-            }
-
-            final int callbackCount = startedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                startedCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingStarted(callback);
     }
 
     @Override
     public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mNotedWatchers.put(callback.asBinder(), callbacks);
-            }
-            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, notedCallback);
-            }
-        }
+        mAppOpsService.startWatchingNoted(ops, callback);
     }
 
     @Override
     public void stopWatchingNoted(IAppOpsNotedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            final SparseArray<NotedCallback> notedCallbacks =
-                    mNotedWatchers.remove(callback.asBinder());
-            if (notedCallbacks == null) {
-                return;
-            }
-            final int callbackCount = notedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                notedCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingNoted(callback);
     }
 
     /**
@@ -2817,7 +772,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2847,7 +802,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2866,7 +821,7 @@
 
         int uid = Binder.getCallingUid();
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -2889,54 +844,49 @@
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
+        int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
+                attributionTag, startIfModeDefault, message,
+                attributionFlags, attributionChainId);
+
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                    packageName);
+
+        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+                resolvedPackageName, attributionTag, null);
+
+        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+            collectAsyncNotedOp(uid, resolvedPackageName, code,
+                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
-        //
-        // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
-            if (result != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(result, code, attributionTag, packageName);
-            }
-        }
-        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
-                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
-                attributionChainId, /*dryRun*/ false);
+        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+                resolvedPackageName);
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+    public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
-        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
-                attributionChainId);
+        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
+                attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                proxiedAttributionFlags, attributionChainId);
     }
 
-    private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
+    private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
             int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
             int attributionChainId) {
+
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
@@ -2984,147 +934,68 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
+            final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
-            if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
-                return testProxiedOp;
+
+            boolean isTestProxiedAttributionTagValid =
+                    mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
+                            proxiedAttributionTag, resolvedProxyPackageName);
+
+            if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
+                return new SyncNotedAppOp(testProxiedOp, code,
+                        isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                        resolvedProxiedPackageName);
             }
 
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
+            final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
-                    shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+                    proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
                     /*dryRun*/ false);
-            if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
-                return proxyAppOp;
+
+            boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+                    resolvedProxyPackageName, proxyAttributionTag, null);
+
+            if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
+                return new SyncNotedAppOp(proxyAppOp, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null,
+                        resolvedProxyPackageName);
+            }
+
+            if (shouldCollectAsyncNotedOp) {
+                collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+                        message, shouldCollectMessage);
             }
         }
 
-        return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
-                proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
-                /*dryRun*/ false);
+        final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+                resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+                proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
+
+        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
+
+        if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
+            collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
+                    isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                    proxiedAttributionFlags, message, shouldCollectMessage);
+        }
+
+        return new SyncNotedAppOp(proxiedAppOp, code,
+                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                resolvedProxiedPackageName);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
         return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
     }
 
-    private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
-            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
-            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-            int attributionChainId, boolean dryRun) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "startOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                    packageName);
-        }
-
-        boolean isRestricted = false;
-        int startType = START_TYPE_FAILED;
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
-                            attributionChainId);
-                }
-                if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName + " flags: "
-                        + AppOpsManager.flagsToString(flags));
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            final UidState uidState = ops.uidState;
-            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
-                    false);
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, uidMode, startType, attributionFlags, attributionChainId);
-                    }
-                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED
-                        && (!startIfModeDefault || mode != MODE_DEFAULT)) {
-                    if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, mode, startType, attributionFlags, attributionChainId);
-                    }
-                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
-                }
-            }
-            if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
-                    + " package " + packageName + " restricted: " + isRestricted
-                    + " flags: " + AppOpsManager.flagsToString(flags));
-            if (!dryRun) {
-                try {
-                    if (isRestricted) {
-                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                    } else {
-                        attributedOp.started(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                        startType = START_TYPE_STARTED;
-                    }
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
-                        attributionChainId);
-            }
-        }
-
-        if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
-            collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
-        }
-
-        return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
-                packageName);
-    }
-
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
@@ -3134,22 +1005,11 @@
 
     private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return;
-        }
-
-        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+        mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
     }
 
     @Override
-    public void finishProxyOperation(@NonNull IBinder clientId, int code,
+    public void finishProxyOperation(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
                 skipProxyOperation);
@@ -3181,8 +1041,8 @@
         }
 
         if (!skipProxyOperation) {
-            finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
-                    proxyAttributionTag);
+            mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
+                    resolvedProxyPackageName, proxyAttributionTag);
         }
 
         String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3191,209 +1051,12 @@
             return null;
         }
 
-        finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag);
+        mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag);
 
         return null;
     }
 
-    private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot finishOperation", e);
-            return;
-        }
-
-        synchronized (this) {
-            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
-                    pvr.bypass, /* edit */ true);
-            if (op == null) {
-                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-
-            if (attributedOp.isRunning() || attributedOp.isPaused()) {
-                attributedOp.finished(clientId);
-            } else {
-                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-            }
-        }
-    }
-
-    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
-            String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
-            int attributionFlags, int attributionChainId) {
-        ArraySet<ActiveCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mActiveWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
-            ActiveCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpActiveChanged,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
-                attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
-            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
-            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
-        // There are features watching for mode changes such as window manager
-        // and location manager which are in our process. The callbacks in these
-        // features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final ActiveCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
-                            active, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
-            String attributionTag, @OpFlags int flags, @Mode int result,
-            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        ArraySet<StartedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mStartedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
-            StartedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpStarted,
-                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
-                result, startedType, attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final StartedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
-                            result, startedType, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
-            String attributionTag, @OpFlags int flags, @Mode int result) {
-        ArraySet<NotedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mNotedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
-            final NotedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpChecked,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
-                result));
-    }
-
-    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result) {
-        // There are features watching for checks in our process. The callbacks in
-        // these features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final NotedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
-                            result);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     @Override
     public int permissionToOpCode(String permission) {
         if (permission == null) {
@@ -3451,13 +1114,6 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
-    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
-                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
-    }
-
     private void verifyIncomingOp(int op) {
         if (op >= 0 && op < AppOpsManager._NUM_OP) {
             // Enforce manage appops permission if it's a restricted read op.
@@ -3498,35 +1154,6 @@
                 || resolveUid(resolvedPackage) != Process.INVALID_UID;
     }
 
-    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
-        if (attributionSource.getUid() != Binder.getCallingUid()
-                && attributionSource.isTrusted(mContext)) {
-            return true;
-        }
-        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState = new UidState(uid);
-            mUidStates.put(uid, uidState);
-        }
-
-        return uidState;
-    }
-
-    private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
-        synchronized (this) {
-            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
-        }
-    }
-
     /**
      * @return {@link PackageManagerInternal}
      */
@@ -3538,764 +1165,6 @@
         return mPackageManagerInternal;
     }
 
-    /**
-     * Create a restriction description matching the properties of the package.
-     *
-     * @param pkg The package to create the restriction description for
-     *
-     * @return The restriction matching the package
-     */
-    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
-        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
-                mContext.checkPermission(android.Manifest.permission
-                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
-                == PackageManager.PERMISSION_GRANTED);
-    }
-
-    /**
-     * @see #verifyAndGetBypass(int, String, String, String)
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag) {
-        return verifyAndGetBypass(uid, packageName, attributionTag, null);
-    }
-
-    /**
-     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
-     * description} for the package, along with a boolean indicating whether the attribution tag is
-     * valid.
-     *
-     * @param uid The uid the package belongs to
-     * @param packageName The package the might belong to the uid
-     * @param attributionTag attribution tag or {@code null} if no need to verify
-     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
-     *
-     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
-     *         attribution tag is valid
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag, @Nullable String proxyPackageName) {
-        if (uid == Process.ROOT_UID) {
-            // For backwards compatibility, don't check package name for root UID.
-            return new PackageVerificationResult(null,
-                    /* isAttributionTagValid */ true);
-        }
-        if (Process.isSdkSandboxUid(uid)) {
-            // SDK sandbox processes run in their own UID range, but their associated
-            // UID for checks should always be the UID of the package implementing SDK sandbox
-            // service.
-            // TODO: We will need to modify the callers of this function instead, so
-            // modifications and checks against the app ops state are done with the
-            // correct UID.
-            try {
-                final PackageManager pm = mContext.getPackageManager();
-                final String supplementalPackageName = pm.getSdkSandboxPackageName();
-                if (Objects.equals(packageName, supplementalPackageName)) {
-                    uid = pm.getPackageUidAsUser(supplementalPackageName,
-                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                // Shouldn't happen for the supplemental package
-                e.printStackTrace();
-            }
-        }
-
-
-        // Do not check if uid/packageName/attributionTag is already known.
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState != null && uidState.pkgOps != null) {
-                Ops ops = uidState.pkgOps.get(packageName);
-
-                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
-                        attributionTag)) && ops.bypass != null) {
-                    return new PackageVerificationResult(ops.bypass,
-                            ops.validAttributionTags.contains(attributionTag));
-                }
-            }
-        }
-
-        int callingUid = Binder.getCallingUid();
-
-        // Allow any attribution tag for resolvable uids
-        int pkgUid;
-        if (Objects.equals(packageName, "com.android.shell")) {
-            // Special case for the shell which is a package but should be able
-            // to bypass app attribution tag restrictions.
-            pkgUid = Process.SHELL_UID;
-        } else {
-            pkgUid = resolveUid(packageName);
-        }
-        if (pkgUid != Process.INVALID_UID) {
-            if (pkgUid != UserHandle.getAppId(uid)) {
-                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
-                        +  UserHandle.getAppId(uid) + otherUidMessage);
-            }
-            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
-                    /* isAttributionTagValid */ true);
-        }
-
-        int userId = UserHandle.getUserId(uid);
-        RestrictionBypass bypass = null;
-        boolean isAttributionTagValid = false;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-            AndroidPackage pkg = pmInt.getPackage(packageName);
-            if (pkg != null) {
-                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
-                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
-                bypass = getBypassforPackage(pkg);
-            }
-            if (!isAttributionTagValid) {
-                AndroidPackage proxyPkg = proxyPackageName != null
-                        ? pmInt.getPackage(proxyPackageName) : null;
-                // Re-check in proxy.
-                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
-                String msg;
-                if (pkg != null && isAttributionTagValid) {
-                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
-                            + " package " + proxyPackageName + ", this is not advised";
-                } else if (pkg != null) {
-                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
-                            + packageName;
-                } else {
-                    msg = "package " + packageName + " not found, can't check for "
-                            + "attributionTag " + attributionTag;
-                }
-
-                try {
-                    if (!mPlatformCompat.isChangeEnabledByPackageName(
-                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
-                            userId) || !mPlatformCompat.isChangeEnabledByUid(
-                                    SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
-                            callingUid)) {
-                        // Do not override tags if overriding is not enabled for this package
-                        isAttributionTagValid = true;
-                    }
-                    Slog.e(TAG, msg);
-                } catch (RemoteException neverHappens) {
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        if (pkgUid != uid) {
-            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
-                    + otherUidMessage);
-        }
-
-        return new PackageVerificationResult(bypass, isAttributionTagValid);
-    }
-
-    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
-            @Nullable String attributionTag) {
-        if (pkg == null) {
-            return false;
-        } else if (attributionTag == null) {
-            return true;
-        }
-        if (pkg.getAttributions() != null) {
-            int numAttributions = pkg.getAttributions().size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Get (and potentially create) ops.
-     *
-     * @param uid The uid the package belongs to
-     * @param packageName The name of the package
-     * @param attributionTag attribution tag
-     * @param isAttributionTagValid whether the given attribution tag is valid
-     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
-     * @param edit If an ops does not exist, create the ops?
-
-     * @return The ops
-     */
-    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
-            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
-        UidState uidState = getUidStateLocked(uid, edit);
-        if (uidState == null) {
-            return null;
-        }
-
-        if (uidState.pkgOps == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState.pkgOps = new ArrayMap<>();
-        }
-
-        Ops ops = uidState.pkgOps.get(packageName);
-        if (ops == null) {
-            if (!edit) {
-                return null;
-            }
-            ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
-        }
-
-        if (edit) {
-            if (bypass != null) {
-                ops.bypass = bypass;
-            }
-
-            if (attributionTag != null) {
-                ops.knownAttributionTags.add(attributionTag);
-                if (isAttributionTagValid) {
-                    ops.validAttributionTags.add(attributionTag);
-                } else {
-                    ops.validAttributionTags.remove(attributionTag);
-                }
-            }
-        }
-
-        return ops;
-    }
-
-    @Override
-    public void scheduleWriteLocked() {
-        if (!mWriteScheduled) {
-            mWriteScheduled = true;
-            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
-        }
-    }
-
-    @Override
-    public void scheduleFastWriteLocked() {
-        if (!mFastWriteScheduled) {
-            mWriteScheduled = true;
-            mFastWriteScheduled = true;
-            mHandler.removeCallbacks(mWriteRunner);
-            mHandler.postDelayed(mWriteRunner, 10*1000);
-        }
-    }
-
-    /**
-     * Get the state of an op for a uid.
-     *
-     * @param code The code of the op
-     * @param uid The uid the of the package
-     * @param packageName The package name for which to get the state for
-     * @param attributionTag The attribution tag
-     * @param isAttributionTagValid Whether the given attribution tag is valid
-     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
-     * @param edit Iff {@code true} create the {@link Op} object if not yet created
-     *
-     * @return The {@link Op state} of the op
-     */
-    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean isAttributionTagValid,
-            @Nullable RestrictionBypass bypass, boolean edit) {
-        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
-                edit);
-        if (ops == null) {
-            return null;
-        }
-        return getOpLocked(ops, code, uid, edit);
-    }
-
-    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
-        Op op = ops.get(code);
-        if (op == null) {
-            if (!edit) {
-                return null;
-            }
-            op = new Op(ops.uidState, ops.packageName, code, uid);
-            ops.put(code, op);
-        }
-        if (edit) {
-            scheduleWriteLocked();
-        }
-        return op;
-    }
-
-    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
-        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
-            return false;
-        }
-        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
-    }
-
-    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
-            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
-        int restrictionSetCount = mOpGlobalRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code)) {
-                return true;
-            }
-        }
-
-        int userHandle = UserHandle.getUserId(uid);
-        restrictionSetCount = mOpUserRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            // For each client, check that the given op is not restricted, or that the given
-            // package is exempt from the restriction.
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
-                    isCheckOp)) {
-                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
-                if (opBypass != null) {
-                    // If we are the system, bypass user restrictions for certain codes
-                    synchronized (this) {
-                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
-                            return false;
-                        }
-                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
-                            return false;
-                        }
-                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
-                                && appBypass.isRecordAudioRestrictionExcept) {
-                            return false;
-                        }
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void readState() {
-        int oldVersion = NO_VERSION;
-        synchronized (mFile) {
-            synchronized (this) {
-                FileInputStream stream;
-                try {
-                    stream = mFile.openRead();
-                } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
-                    return;
-                }
-                boolean success = false;
-                mUidStates.clear();
-                mAppOpsServiceInterface.clearAllModes();
-                try {
-                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-                    int type;
-                    while ((type = parser.next()) != XmlPullParser.START_TAG
-                            && type != XmlPullParser.END_DOCUMENT) {
-                        ;
-                    }
-
-                    if (type != XmlPullParser.START_TAG) {
-                        throw new IllegalStateException("no start tag found");
-                    }
-
-                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
-
-                    int outerDepth = parser.getDepth();
-                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                            continue;
-                        }
-
-                        String tagName = parser.getName();
-                        if (tagName.equals("pkg")) {
-                            readPackage(parser);
-                        } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
-                        } else {
-                            Slog.w(TAG, "Unknown element under <app-ops>: "
-                                    + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                        }
-                    }
-                    success = true;
-                } catch (IllegalStateException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NullPointerException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NumberFormatException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (XmlPullParserException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IndexOutOfBoundsException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } finally {
-                    if (!success) {
-                        mUidStates.clear();
-                        mAppOpsServiceInterface.clearAllModes();
-                    }
-                    try {
-                        stream.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-        synchronized (this) {
-            upgradeLocked(oldVersion);
-        }
-    }
-
-    private void upgradeRunAnyInBackgroundLocked() {
-        for (int i = 0; i < mUidStates.size(); i++) {
-            final UidState uidState = mUidStates.valueAt(i);
-            if (uidState == null) {
-                continue;
-            }
-            SparseIntArray opModes = uidState.getNonDefaultUidModes();
-            if (opModes != null) {
-                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                            opModes.valueAt(idx));
-                }
-            }
-            if (uidState.pkgOps == null) {
-                continue;
-            }
-            boolean changed = false;
-            for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                Ops ops = uidState.pkgOps.valueAt(j);
-                if (ops != null) {
-                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
-                        final Op copy = new Op(op.uidState, op.packageName,
-                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
-                        copy.setMode(op.getMode());
-                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
-                        changed = true;
-                    }
-                }
-            }
-            if (changed) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
-    private void upgradeLocked(int oldVersion) {
-        if (oldVersion >= CURRENT_VERSION) {
-            return;
-        }
-        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
-        switch (oldVersion) {
-            case NO_VERSION:
-                upgradeRunAnyInBackgroundLocked();
-                // fall through
-            case 1:
-                // for future upgrades
-        }
-        scheduleFastWriteLocked();
-    }
-
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-                setUidMode(code, uid, mode);
-            } else {
-                Slog.w(TAG, "Unknown element under <uid-ops>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readPackage(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readUid(TypedXmlPullParser parser, String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int uid = parser.getAttributeInt(null, "n");
-        final UidState uidState = getUidStateLocked(uid, true);
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, uidState, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-        uidState.evalForegroundOps();
-    }
-
-    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
-            @Nullable String attribution)
-            throws NumberFormatException, IOException, XmlPullParserException {
-        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
-        final long key = parser.getAttributeLong(null, "n");
-        final int uidState = extractUidStateFromKey(key);
-        final int opFlags = extractFlagsFromKey(key);
-
-        final long accessTime = parser.getAttributeLong(null, "t", 0);
-        final long rejectTime = parser.getAttributeLong(null, "r", 0);
-        final long accessDuration = parser.getAttributeLong(null, "d", -1);
-        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
-        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
-        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
-        if (accessTime > 0) {
-            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
-                    proxyAttributionTag, uidState, opFlags);
-        }
-        if (rejectTime > 0) {
-            attributedOp.rejected(rejectTime, uidState, opFlags);
-        }
-    }
-
-    private void readOp(TypedXmlPullParser parser,
-            @NonNull UidState uidState, @NonNull String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int opCode = parser.getAttributeInt(null, "n");
-        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
-        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
-        op.setMode(mode);
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("st")) {
-                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
-            } else {
-                Slog.w(TAG, "Unknown element under <op>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-
-        if (uidState.pkgOps == null) {
-            uidState.pkgOps = new ArrayMap<>();
-        }
-        Ops ops = uidState.pkgOps.get(pkgName);
-        if (ops == null) {
-            ops = new Ops(pkgName, uidState);
-            uidState.pkgOps.put(pkgName, ops);
-        }
-        ops.put(op.op, op);
-    }
-
-    void writeState() {
-        synchronized (mFile) {
-            FileOutputStream stream;
-            try {
-                stream = mFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state: " + e);
-                return;
-            }
-
-            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
-            try {
-                TypedXmlSerializer out = Xml.resolveSerializer(stream);
-                out.startDocument(null, true);
-                out.startTag(null, "app-ops");
-                out.attributeInt(null, "v", CURRENT_VERSION);
-
-                SparseArray<SparseIntArray> uidStatesClone;
-                synchronized (this) {
-                    uidStatesClone = new SparseArray<>(mUidStates.size());
-
-                    final int uidStateCount = mUidStates.size();
-                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                        UidState uidState = mUidStates.valueAt(uidStateNum);
-                        int uid = mUidStates.keyAt(uidStateNum);
-
-                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                        if (opModes != null && opModes.size() > 0) {
-                            uidStatesClone.put(uid, opModes);
-                        }
-                    }
-                }
-
-                final int uidStateCount = uidStatesClone.size();
-                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
-                    if (opModes != null && opModes.size() > 0) {
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
-                        final int opCount = opModes.size();
-                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                            final int op = opModes.keyAt(opCountNum);
-                            final int mode = opModes.valueAt(opCountNum);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op);
-                            out.attributeInt(null, "m", mode);
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                }
-
-                if (allOps != null) {
-                    String lastPkg = null;
-                    for (int i=0; i<allOps.size(); i++) {
-                        AppOpsManager.PackageOps pkg = allOps.get(i);
-                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
-                            if (lastPkg != null) {
-                                out.endTag(null, "pkg");
-                            }
-                            lastPkg = pkg.getPackageName();
-                            if (lastPkg != null) {
-                                out.startTag(null, "pkg");
-                                out.attribute(null, "n", lastPkg);
-                            }
-                        }
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", pkg.getUid());
-                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
-                        for (int j=0; j<ops.size(); j++) {
-                            AppOpsManager.OpEntry op = ops.get(j);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op.getOp());
-                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
-                                out.attributeInt(null, "m", op.getMode());
-                            }
-
-                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
-                                final AttributedOpEntry attribution =
-                                        op.getAttributedOpEntries().get(attributionTag);
-
-                                final ArraySet<Long> keys = attribution.collectKeys();
-
-                                final int keyCount = keys.size();
-                                for (int k = 0; k < keyCount; k++) {
-                                    final long key = keys.valueAt(k);
-
-                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
-                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-                                    final long accessTime = attribution.getLastAccessTime(uidState,
-                                            uidState, flags);
-                                    final long rejectTime = attribution.getLastRejectTime(uidState,
-                                            uidState, flags);
-                                    final long accessDuration = attribution.getLastDuration(
-                                            uidState, uidState, flags);
-                                    // Proxy information for rejections is not backed up
-                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
-                                            uidState, uidState, flags);
-
-                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
-                                            && proxy == null) {
-                                        continue;
-                                    }
-
-                                    String proxyPkg = null;
-                                    String proxyAttributionTag = null;
-                                    int proxyUid = Process.INVALID_UID;
-                                    if (proxy != null) {
-                                        proxyPkg = proxy.getPackageName();
-                                        proxyAttributionTag = proxy.getAttributionTag();
-                                        proxyUid = proxy.getUid();
-                                    }
-
-                                    out.startTag(null, "st");
-                                    if (attributionTag != null) {
-                                        out.attribute(null, "id", attributionTag);
-                                    }
-                                    out.attributeLong(null, "n", key);
-                                    if (accessTime > 0) {
-                                        out.attributeLong(null, "t", accessTime);
-                                    }
-                                    if (rejectTime > 0) {
-                                        out.attributeLong(null, "r", rejectTime);
-                                    }
-                                    if (accessDuration > 0) {
-                                        out.attributeLong(null, "d", accessDuration);
-                                    }
-                                    if (proxyPkg != null) {
-                                        out.attribute(null, "pp", proxyPkg);
-                                    }
-                                    if (proxyAttributionTag != null) {
-                                        out.attribute(null, "pc", proxyAttributionTag);
-                                    }
-                                    if (proxyUid >= 0) {
-                                        out.attributeInt(null, "pu", proxyUid);
-                                    }
-                                    out.endTag(null, "st");
-                                }
-                            }
-
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                    if (lastPkg != null) {
-                        out.endTag(null, "pkg");
-                    }
-                }
-
-                out.endTag(null, "app-ops");
-                out.endDocument();
-                mFile.finishWrite(stream);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state, restoring backup.", e);
-                mFile.failWrite(stream);
-            }
-        }
-        mHistoricalRegistry.writeAndClearDiscreteHistory();
-    }
-
     static class Shell extends ShellCommand {
         final IAppOpsService mInterface;
         final AppOpsService mInternal;
@@ -4309,7 +1178,6 @@
         int mode;
         int packageUid;
         int nonpackageUid;
-        final static Binder sBinder = new Binder();
         IBinder mToken;
         boolean targetsUid;
 
@@ -4330,7 +1198,7 @@
             dumpCommandHelp(pw);
         }
 
-        static private int strOpToOp(String op, PrintWriter err) {
+        static int strOpToOp(String op, PrintWriter err) {
             try {
                 return AppOpsManager.strOpToOp(op);
             } catch (IllegalArgumentException e) {
@@ -4527,6 +1395,24 @@
         pw.println("              not specified, the current user is assumed.");
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mAppOpsService.dump(fd, pw, args);
+
+        pw.println();
+        if (mCheckOpsDelegateDispatcher.mPolicy != null
+                && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+            AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+            policy.dumpTags(pw);
+        } else {
+            pw.println("  AppOps policy not set.");
+        }
+
+        if (mAudioRestrictionManager.hasActiveRestrictions()) {
+            pw.println();
+            mAudioRestrictionManager.dump(pw);
+        }
+    }
     static int onShellCommand(Shell shell, String cmd) {
         if (cmd == null) {
             return shell.handleDefaultCommands(cmd);
@@ -4730,14 +1616,12 @@
                     return 0;
                 }
                 case "write-settings": {
-                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+                    shell.mInternal.mAppOpsService
+                            .enforceManageAppOpsModes(Binder.getCallingPid(),
                             Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        synchronized (shell.mInternal) {
-                            shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
-                        }
-                        shell.mInternal.writeState();
+                        shell.mInternal.mAppOpsService.writeState();
                         pw.println("Current settings written.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4745,11 +1629,12 @@
                     return 0;
                 }
                 case "read-settings": {
-                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
-                            Binder.getCallingUid(), -1);
+                    shell.mInternal.mAppOpsService
+                            .enforceManageAppOpsModes(Binder.getCallingPid(),
+                                    Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        shell.mInternal.readState();
+                        shell.mInternal.mAppOpsService.readState();
                         pw.println("Last settings read.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4795,877 +1680,70 @@
         return -1;
     }
 
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("AppOps service (appops) dump options:");
-        pw.println("  -h");
-        pw.println("    Print this help text.");
-        pw.println("  --op [OP]");
-        pw.println("    Limit output to data associated with the given app op code.");
-        pw.println("  --mode [MODE]");
-        pw.println("    Limit output to data associated with the given app op mode.");
-        pw.println("  --package [PACKAGE]");
-        pw.println("    Limit output to data associated with the given package name.");
-        pw.println("  --attributionTag [attributionTag]");
-        pw.println("    Limit output to data associated with the given attribution tag.");
-        pw.println("  --include-discrete [n]");
-        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
-        pw.println("  --watchers");
-        pw.println("    Only output the watcher sections.");
-        pw.println("  --history");
-        pw.println("    Only output history.");
-        pw.println("  --uid-state-changes");
-        pw.println("    Include logs about uid state changes.");
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
-            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
-            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
-        final int numAttributions = op.mAttributions.size();
-        for (int i = 0; i < numAttributions; i++) {
-            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
-                    op.mAttributions.keyAt(i), filterAttributionTag)) {
-                continue;
-            }
-
-            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
-            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
-                    prefix + "  ");
-            pw.print(prefix + "]\n");
-        }
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
-            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
-            @NonNull Date date, @NonNull String prefix) {
-
-        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
-                attributionTag).getAttributedOpEntries().get(attributionTag);
-
-        final ArraySet<Long> keys = entry.collectKeys();
-
-        final int keyCount = keys.size();
-        for (int k = 0; k < keyCount; k++) {
-            final long key = keys.valueAt(k);
-
-            final int uidState = AppOpsManager.extractUidStateFromKey(key);
-            final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
-            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
-            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
-            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
-            String proxyPkg = null;
-            String proxyAttributionTag = null;
-            int proxyUid = Process.INVALID_UID;
-            if (proxy != null) {
-                proxyPkg = proxy.getPackageName();
-                proxyAttributionTag = proxy.getAttributionTag();
-                proxyUid = proxy.getUid();
-            }
-
-            if (accessTime > 0) {
-                pw.print(prefix);
-                pw.print("Access: ");
-                pw.print(AppOpsManager.keyToString(key));
-                pw.print(" ");
-                date.setTime(accessTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(accessTime - now, pw);
-                pw.print(")");
-                if (accessDuration > 0) {
-                    pw.print(" duration=");
-                    TimeUtils.formatDuration(accessDuration, pw);
-                }
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-
-            if (rejectTime > 0) {
-                pw.print(prefix);
-                pw.print("Reject: ");
-                pw.print(AppOpsManager.keyToString(key));
-                date.setTime(rejectTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(rejectTime - now, pw);
-                pw.print(")");
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-        }
-
-        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-        if (attributedOp.isRunning()) {
-            long earliestElapsedTime = Long.MAX_VALUE;
-            long maxNumStarts = 0;
-            int numInProgressEvents = attributedOp.mInProgressEvents.size();
-            for (int i = 0; i < numInProgressEvents; i++) {
-                AttributedOp.InProgressStartOpEvent event =
-                        attributedOp.mInProgressEvents.valueAt(i);
-
-                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
-                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
-            }
-
-            pw.print(prefix + "Running start at: ");
-            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
-            pw.println();
-
-            if (maxNumStarts > 1) {
-                pw.print(prefix + "startNesting=");
-                pw.println(maxNumStarts);
-            }
-        }
-    }
-
-    @NeverCompile // Avoid size overhead of debugging code.
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
-        int dumpOp = OP_NONE;
-        String dumpPackage = null;
-        String dumpAttributionTag = null;
-        int dumpUid = Process.INVALID_UID;
-        int dumpMode = -1;
-        boolean dumpWatchers = false;
-        // TODO ntmyren: Remove the dumpHistory and dumpFilter
-        boolean dumpHistory = false;
-        boolean includeDiscreteOps = false;
-        boolean dumpUidStateChangeLogs = false;
-        int nDiscreteOps = 10;
-        @HistoricalOpsRequestFilter int dumpFilter = 0;
-        boolean dumpAll = false;
-
-        if (args != null) {
-            for (int i = 0; i < args.length; i++) {
-                String arg = args[i];
-                if ("-h".equals(arg)) {
-                    dumpHelp(pw);
-                    return;
-                } else if ("-a".equals(arg)) {
-                    // dump all data
-                    dumpAll = true;
-                } else if ("--op".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --op option");
-                        return;
-                    }
-                    dumpOp = Shell.strOpToOp(args[i], pw);
-                    dumpFilter |= FILTER_BY_OP_NAMES;
-                    if (dumpOp < 0) {
-                        return;
-                    }
-                } else if ("--package".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --package option");
-                        return;
-                    }
-                    dumpPackage = args[i];
-                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
-                    try {
-                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
-                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
-                                0);
-                    } catch (RemoteException e) {
-                    }
-                    if (dumpUid < 0) {
-                        pw.println("Unknown package: " + dumpPackage);
-                        return;
-                    }
-                    dumpUid = UserHandle.getAppId(dumpUid);
-                    dumpFilter |= FILTER_BY_UID;
-                } else if ("--attributionTag".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --attributionTag option");
-                        return;
-                    }
-                    dumpAttributionTag = args[i];
-                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
-                } else if ("--mode".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --mode option");
-                        return;
-                    }
-                    dumpMode = Shell.strModeToMode(args[i], pw);
-                    if (dumpMode < 0) {
-                        return;
-                    }
-                } else if ("--watchers".equals(arg)) {
-                    dumpWatchers = true;
-                } else if ("--include-discrete".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --include-discrete option");
-                        return;
-                    }
-                    try {
-                        nDiscreteOps = Integer.valueOf(args[i]);
-                    } catch (NumberFormatException e) {
-                        pw.println("Wrong parameter: " + args[i]);
-                        return;
-                    }
-                    includeDiscreteOps = true;
-                } else if ("--history".equals(arg)) {
-                    dumpHistory = true;
-                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
-                    pw.println("Unknown option: " + arg);
-                    return;
-                } else if ("--uid-state-changes".equals(arg)) {
-                    dumpUidStateChangeLogs = true;
-                } else {
-                    pw.println("Unknown command: " + arg);
-                    return;
-                }
-            }
-        }
-
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        final Date date = new Date();
-        synchronized (this) {
-            pw.println("Current AppOps Service state:");
-            if (!dumpHistory && !dumpWatchers) {
-                mConstants.dump(pw);
-            }
-            pw.println();
-            final long now = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            final long nowUptime = SystemClock.uptimeMillis();
-            boolean needSep = false;
-            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
-                    && !dumpHistory) {
-                pw.println("  Profile owners:");
-                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
-                    pw.print("    User #");
-                    pw.print(mProfileOwners.keyAt(poi));
-                    pw.print(": ");
-                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
-                    pw.println();
-                }
-                pw.println();
-            }
-
-            if (!dumpHistory) {
-                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
-            }
-
-            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
-                boolean printedHeader = false;
-                for (int i = 0; i < mModeWatchers.size(); i++) {
-                    final ModeCallback cb = mModeWatchers.valueAt(i);
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        pw.println("  All op mode watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
-                    pw.print(": "); pw.println(cb);
-                }
-            }
-            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
-                    final SparseArray<ActiveCallback> activeWatchers =
-                            mActiveWatchers.valueAt(watcherNum);
-                    if (activeWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final ActiveCallback cb = activeWatchers.valueAt(0);
-                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op active watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mActiveWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = activeWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-
-                final int watchersSize = mStartedWatchers.size();
-                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
-                    final SparseArray<StartedCallback> startedWatchers =
-                            mStartedWatchers.valueAt(watcherNum);
-                    if (startedWatchers.size() <= 0) {
-                        continue;
-                    }
-
-                    final StartedCallback cb = startedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-
-                    if (!printedHeader) {
-                        pw.println("  All op started watchers:");
-                        printedHeader = true;
-                    }
-
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mStartedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-
-                    pw.print("        [");
-                    final int opCount = startedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-
-                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
-                    final SparseArray<NotedCallback> notedWatchers =
-                            mNotedWatchers.valueAt(watcherNum);
-                    if (notedWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final NotedCallback cb = notedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op noted watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mNotedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = notedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
-                    && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
-                needSep = mAudioRestrictionManager.dump(pw) || needSep;
-            }
-            if (needSep) {
-                pw.println();
-            }
-            for (int i=0; i<mUidStates.size(); i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
-                if (dumpWatchers || dumpHistory) {
-                    continue;
-                }
-                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
-                    boolean hasOp = dumpOp < 0 || (opModes != null
-                            && opModes.indexOfKey(dumpOp) >= 0);
-                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
-                    boolean hasMode = dumpMode < 0;
-                    if (!hasMode && opModes != null) {
-                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
-                            if (opModes.valueAt(opi) == dumpMode) {
-                                hasMode = true;
-                            }
-                        }
-                    }
-                    if (pkgOps != null) {
-                        for (int pkgi = 0;
-                                 (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
-                                 pkgi++) {
-                            Ops ops = pkgOps.valueAt(pkgi);
-                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
-                                hasOp = true;
-                            }
-                            if (!hasMode) {
-                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
-                                    if (ops.valueAt(opi).getMode() == dumpMode) {
-                                        hasMode = true;
-                                    }
-                                }
-                            }
-                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
-                                hasPackage = true;
-                            }
-                        }
-                    }
-                    if (uidState.foregroundOps != null && !hasOp) {
-                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
-                            hasOp = true;
-                        }
-                    }
-                    if (!hasOp || !hasPackage || !hasMode) {
-                        continue;
-                    }
-                }
-
-                pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
-                uidState.dump(pw, nowElapsed);
-                if (uidState.foregroundOps != null && (dumpMode < 0
-                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
-                    pw.println("    foregroundOps:");
-                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
-                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
-                            continue;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
-                        pw.print(": ");
-                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
-                    }
-                    pw.print("    hasForegroundWatchers=");
-                    pw.println(uidState.hasForegroundWatchers);
-                }
-                needSep = true;
-
-                if (opModes != null) {
-                    final int opModeCount = opModes.size();
-                    for (int j = 0; j < opModeCount; j++) {
-                        final int code = opModes.keyAt(j);
-                        final int mode = opModes.valueAt(j);
-                        if (dumpOp >= 0 && dumpOp != code) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != mode) {
-                            continue;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
-                    }
-                }
-
-                if (pkgOps == null) {
-                    continue;
-                }
-
-                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
-                    final Ops ops = pkgOps.valueAt(pkgi);
-                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
-                        continue;
-                    }
-                    boolean printedPackage = false;
-                    for (int j=0; j<ops.size(); j++) {
-                        final Op op = ops.valueAt(j);
-                        final int opCode = op.op;
-                        if (dumpOp >= 0 && dumpOp != opCode) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
-                            continue;
-                        }
-                        if (!printedPackage) {
-                            pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
-                            printedPackage = true;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(opCode));
-                        pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
-                        final int switchOp = AppOpsManager.opToSwitch(opCode);
-                        if (switchOp != opCode) {
-                            pw.print(" / switch ");
-                            pw.print(AppOpsManager.opToName(switchOp));
-                            final Op switchObj = ops.get(switchOp);
-                            int mode = switchObj == null
-                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
-                            pw.print("="); pw.print(AppOpsManager.modeToName(mode));
-                        }
-                        pw.println("): ");
-                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
-                                sdf, date, "        ");
-                    }
-                }
-            }
-            if (needSep) {
-                pw.println();
-            }
-
-            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
-            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
-            if (!dumpHistory && !dumpWatchers) {
-                pw.println();
-                if (mCheckOpsDelegateDispatcher.mPolicy != null
-                        && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
-                    AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
-                    policy.dumpTags(pw);
-                } else {
-                    pw.println("  AppOps policy not set.");
-                }
-            }
-
-            if (dumpAll || dumpUidStateChangeLogs) {
-                pw.println();
-                pw.println("Uid State Changes Event Log:");
-                getUidStateTracker().dumpEvents(pw);
-            }
-        }
-
-        // Must not hold the appops lock
-        if (dumpHistory && !dumpWatchers) {
-            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
-                    dumpFilter);
-        }
-        if (includeDiscreteOps) {
-            pw.println("Discrete accesses: ");
-            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
-                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
-        }
-    }
-
     @Override
     public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
-        checkSystemUid("setUserRestrictions");
-        Objects.requireNonNull(restrictions);
-        Objects.requireNonNull(token);
-        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
-            String restriction = AppOpsManager.opToRestriction(i);
-            if (restriction != null) {
-                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
-                        userHandle, null);
-            }
-        }
+        mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
     }
 
     @Override
     public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
             PackageTagsList excludedPackageTags) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), null);
-        }
-        if (userHandle != UserHandle.getCallingUserId()) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
-                && mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
-                        + " INTERACT_ACROSS_USERS to interact cross user ");
-            }
-        }
-        verifyIncomingOp(code);
-        Objects.requireNonNull(token);
-        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
-    }
-
-    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
-            int userHandle, PackageTagsList excludedPackageTags) {
-        synchronized (AppOpsService.this) {
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
-            if (restrictionState == null) {
-                try {
-                    restrictionState = new ClientUserRestrictionState(token);
-                } catch (RemoteException e) {
-                    return;
-                }
-                mOpUserRestrictions.put(token, restrictionState);
-            }
-
-            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
-                    userHandle)) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::updateStartedOpModeForUser, this, code, restricted,
-                        userHandle));
-            }
-
-            if (restrictionState.isDefault()) {
-                mOpUserRestrictions.remove(token);
-                restrictionState.destroy();
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
-        synchronized (AppOpsService.this) {
-            int numUids = mUidStates.size();
-            for (int uidNum = 0; uidNum < numUids; uidNum++) {
-                int uid = mUidStates.keyAt(uidNum);
-                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
-                    continue;
-                }
-                updateStartedOpModeForUidLocked(code, restricted, uid);
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null || uidState.pkgOps == null) {
-            return;
-        }
-
-        int numPkgOps = uidState.pkgOps.size();
-        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
-            Ops ops = uidState.pkgOps.valueAt(pkgNum);
-            Op op = ops != null ? ops.get(code) : null;
-            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
-                continue;
-            }
-            int numAttrTags = op.mAttributions.size();
-            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
-                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
-                if (restricted && attrOp.isRunning()) {
-                    attrOp.pause();
-                } else if (attrOp.isPaused()) {
-                    attrOp.resume();
-                }
-            }
-        }
-    }
-
-    private void notifyWatchersOfChange(int code, int uid) {
-        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
-        synchronized (this) {
-            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
-            if (modeChangedListenerSet == null) {
-                return;
-            }
-        }
-
-        notifyOpChanged(modeChangedListenerSet,  code, uid, null);
+        mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
+                excludedPackageTags);
     }
 
     @Override
     public void removeUser(int userHandle) throws RemoteException {
-        checkSystemUid("removeUser");
-        synchronized (AppOpsService.this) {
-            final int tokenCount = mOpUserRestrictions.size();
-            for (int i = tokenCount - 1; i >= 0; i--) {
-                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
-                opRestrictions.removeUser(userHandle);
-            }
-            removeUidsForUserLocked(userHandle);
-        }
+        mAppOpsService.removeUser(userHandle);
     }
 
     @Override
     public boolean isOperationActive(int code, int uid, String packageName) {
-        if (Binder.getCallingUid() != uid) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                return false;
-            }
-        }
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return false;
-        }
-
-        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return false;
-        }
-        // TODO moltmann: Allow to check for attribution op activeness
-        synchronized (AppOpsService.this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
-            if (pkgOps == null) {
-                return false;
-            }
-
-            Op op = pkgOps.get(code);
-            if (op == null) {
-                return false;
-            }
-
-            return op.isRunning();
-        }
+        return mAppOpsService.isOperationActive(code, uid, packageName);
     }
 
     @Override
     public boolean isProxying(int op, @NonNull String proxyPackageName,
             @NonNull String proxyAttributionTag, int proxiedUid,
             @NonNull String proxiedPackageName) {
-        Objects.requireNonNull(proxyPackageName);
-        Objects.requireNonNull(proxiedPackageName);
-        final long callingUid = Binder.getCallingUid();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
-                    proxiedPackageName, new int[] {op});
-            if (packageOps == null || packageOps.isEmpty()) {
-                return false;
-            }
-            final List<OpEntry> opEntries = packageOps.get(0).getOps();
-            if (opEntries.isEmpty()) {
-                return false;
-            }
-            final OpEntry opEntry = opEntries.get(0);
-            if (!opEntry.isRunning()) {
-                return false;
-            }
-            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
-                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
-            return proxyInfo != null && callingUid == proxyInfo.getUid()
-                    && proxyPackageName.equals(proxyInfo.getPackageName())
-                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
+                proxiedUid, proxiedPackageName);
     }
 
     @Override
     public void resetPackageOpsNoHistory(@NonNull String packageName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetPackageOpsNoHistory");
-        synchronized (AppOpsService.this) {
-            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
-                    UserHandle.getCallingUserId());
-            if (uid == Process.INVALID_UID) {
-                return;
-            }
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null || uidState.pkgOps == null) {
-                return;
-            }
-            Ops removedOps = uidState.pkgOps.remove(packageName);
-            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-            }
-        }
+        mAppOpsService.resetPackageOpsNoHistory(packageName);
     }
 
     @Override
     public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
             long baseSnapshotInterval, int compressionStep) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "setHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+        mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
     }
 
     @Override
     public void offsetHistory(long offsetMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "offsetHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.offsetHistory(offsetMillis);
-        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+        mAppOpsService.offsetHistory(offsetMillis);
     }
 
     @Override
     public void addHistoricalOps(HistoricalOps ops) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "addHistoricalOps");
-        // Must not hold the appops lock
-        mHistoricalRegistry.addHistoricalOps(ops);
+        mAppOpsService.addHistoricalOps(ops);
     }
 
     @Override
     public void resetHistoryParameters() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.resetHistoryParameters();
+        mAppOpsService.resetHistoryParameters();
     }
 
     @Override
     public void clearHistory() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "clearHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.clearAllHistory();
+        mAppOpsService.clearHistory();
     }
 
     @Override
     public void rebootHistory(long offlineDurationMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "rebootHistory");
-
-        Preconditions.checkArgument(offlineDurationMillis >= 0);
-
-        // Must not hold the appops lock
-        mHistoricalRegistry.shutdown();
-
-        if (offlineDurationMillis > 0) {
-            SystemClock.sleep(offlineDurationMillis);
-        }
-
-        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-        mHistoricalRegistry.persistPendingHistory();
+        mAppOpsService.rebootHistory(offlineDurationMillis);
     }
 
     /**
@@ -5920,24 +1998,6 @@
         return false;
     }
 
-    @GuardedBy("this")
-    private void removeUidsForUserLocked(int userHandle) {
-        for (int i = mUidStates.size() - 1; i >= 0; --i) {
-            final int uid = mUidStates.keyAt(i);
-            if (UserHandle.getUserId(uid) == userHandle) {
-                mUidStates.valueAt(i).clear();
-                mUidStates.removeAt(i);
-            }
-        }
-    }
-
-    private void checkSystemUid(String function) {
-        int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID) {
-            throw new SecurityException(function + " must by called by the system");
-        }
-    }
-
     private static int resolveUid(String packageName)  {
         if (packageName == null) {
             return Process.INVALID_UID;
@@ -5958,184 +2018,43 @@
         return Process.INVALID_UID;
     }
 
-    private static String[] getPackagesForUid(int uid) {
-        String[] packageNames = null;
-
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        if (AppGlobals.getPackageManager() != null) {
-            try {
-                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-        if (packageNames == null) {
-            return EmptyArray.STRING;
-        }
-        return packageNames;
-    }
-
-    private final class ClientUserRestrictionState implements DeathRecipient {
-        private final IBinder token;
-
-        ClientUserRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.token = token;
-        }
-
-        public boolean setRestriction(int code, boolean restricted,
-                PackageTagsList excludedPackageTags, int userId) {
-            return mAppOpsRestrictions.setUserRestriction(token, userId, code,
-                    restricted, excludedPackageTags);
-        }
-
-        public boolean hasRestriction(int code, String packageName, String attributionTag,
-                int userId, boolean isCheckOp) {
-            return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
-                    attributionTag, isCheckOp);
-        }
-
-        public void removeUser(int userId) {
-            mAppOpsRestrictions.clearUserRestrictions(token, userId);
-        }
-
-        public boolean isDefault() {
-            return !mAppOpsRestrictions.hasUserRestrictions(token);
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (AppOpsService.this) {
-                mAppOpsRestrictions.clearUserRestrictions(token);
-                mOpUserRestrictions.remove(token);
-                destroy();
-            }
-        }
-
-        public void destroy() {
-            token.unlinkToDeath(this, 0);
-        }
-    }
-
-    private final class ClientGlobalRestrictionState implements DeathRecipient {
-        final IBinder mToken;
-
-        ClientGlobalRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.mToken = token;
-        }
-
-        boolean setRestriction(int code, boolean restricted) {
-            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
-        }
-
-        boolean hasRestriction(int code) {
-            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
-        }
-
-        boolean isDefault() {
-            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
-        }
-
-        @Override
-        public void binderDied() {
-            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
-            mOpGlobalRestrictions.remove(mToken);
-            destroy();
-        }
-
-        void destroy() {
-            mToken.unlinkToDeath(this, 0);
-        }
-    }
-
     private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
         @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
-            synchronized (AppOpsService.this) {
-                mProfileOwners = owners;
-            }
+            AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
         }
 
         @Override
         public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
                 boolean visible) {
-            AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
+            AppOpsService.this.mAppOpsService
+                    .updateAppWidgetVisibility(uidPackageNames, visible);
         }
 
         @Override
         public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
                 @Nullable IAppOpsCallback callback) {
-            setUidMode(code, uid, mode, callback);
+            AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
         }
 
         @Override
         public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
                 int mode, @Nullable IAppOpsCallback callback) {
-            setMode(code, uid, packageName, mode, callback);
+            AppOpsService.this.mAppOpsService
+                    .setMode(code, uid, packageName, mode, callback);
         }
 
 
         @Override
         public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
-            if (Binder.getCallingPid() != Process.myPid()) {
-                // TODO instead of this enforcement put in AppOpsManagerInternal
-                throw new SecurityException("Only the system can set global restrictions");
-            }
-
-            synchronized (AppOpsService.this) {
-                ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
-                if (restrictionState == null) {
-                    try {
-                        restrictionState = new ClientGlobalRestrictionState(token);
-                    } catch (RemoteException  e) {
-                        return;
-                    }
-                    mOpGlobalRestrictions.put(token, restrictionState);
-                }
-
-                if (restrictionState.setRestriction(code, restricted)) {
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
-                            UID_ANY));
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
-                            code, restricted, UserHandle.USER_ALL));
-                }
-
-                if (restrictionState.isDefault()) {
-                    mOpGlobalRestrictions.remove(token);
-                    restrictionState.destroy();
-                }
-            }
+            AppOpsService.this.mAppOpsService
+                    .setGlobalRestriction(code, restricted, token);
         }
 
         @Override
         public int getOpRestrictionCount(int code, UserHandle user, String pkg,
                 String attributionTag) {
-            int number = 0;
-            synchronized (AppOpsService.this) {
-                int numRestrictions = mOpUserRestrictions.size();
-                for (int i = 0; i < numRestrictions; i++) {
-                    if (mOpUserRestrictions.valueAt(i)
-                            .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
-                                    false)) {
-                        number++;
-                    }
-                }
-
-                numRestrictions = mOpGlobalRestrictions.size();
-                for (int i = 0; i < numRestrictions; i++) {
-                    if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
-                        number++;
-                    }
-                }
-            }
-
-            return number;
+            return AppOpsService.this.mAppOpsService
+                    .getOpRestrictionCount(code, user, pkg, attributionTag);
         }
     }
 
@@ -6431,7 +2350,7 @@
                     attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
 
-        public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+        public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6461,7 +2380,7 @@
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
-        private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+        private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6495,7 +2414,7 @@
                     AppOpsService.this::finishOperationImpl);
         }
 
-        public void finishProxyOperation(@NonNull IBinder clientId, int code,
+        public void finishProxyOperation(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
@@ -6513,7 +2432,7 @@
             }
         }
 
-        private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+        private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
                     skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
new file mode 100644
index 0000000..70f3bcc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -0,0 +1,4679 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.AttributedOpEntry;
+import static android.app.AppOpsManager.AttributionFlags;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOps;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.Mode;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEntry;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.OpFlags;
+import static android.app.AppOpsManager.RestrictionBypass;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
+
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.PackageTagsList;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+class AppOpsServiceImpl implements AppOpsServiceInterface {
+    static final String TAG = "AppOps";
+    static final boolean DEBUG = false;
+
+    private static final int NO_VERSION = -1;
+    /**
+     * Increment by one every time and add the corresponding upgrade logic in
+     * {@link #upgradeLocked(int)} below. The first version was 1
+     */
+    private static final int CURRENT_VERSION = 1;
+
+    // Write at most every 30 minutes.
+    static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
+
+    // Constant meaning that any UID should be matched when dispatching callbacks
+    private static final int UID_ANY = -2;
+
+    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+            OP_PLAY_AUDIO,
+            OP_RECORD_AUDIO,
+            OP_CAMERA,
+            OP_VIBRATE,
+    };
+    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
+    final Context mContext;
+    final AtomicFile mFile;
+    final Handler mHandler;
+
+    /**
+     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+     * objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+    /**
+     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+     * new objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+                    MAX_UNUSED_POOLED_OBJECTS);
+    @Nullable
+    private final DevicePolicyManagerInternal dpmi =
+            LocalServices.getService(DevicePolicyManagerInternal.class);
+
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+    boolean mWriteScheduled;
+    boolean mFastWriteScheduled;
+    final Runnable mWriteRunner = new Runnable() {
+        public void run() {
+            synchronized (AppOpsServiceImpl.this) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        writeState();
+                        return null;
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+            }
+        }
+    };
+
+    @GuardedBy("this")
+    @VisibleForTesting
+    final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+    /*
+     * These are app op restrictions imposed per user from various parties.
+     */
+    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+            new ArrayMap<>();
+
+    /*
+     * These are app op restrictions imposed globally from various parties within the system.
+     */
+    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+            new ArrayMap<>();
+
+    SparseIntArray mProfileOwners;
+
+    /**
+     * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+     * changed
+     */
+    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+    /**
+     * Package Manager internal. Access via {@link #getPackageManagerInternal()}
+     */
+    private @Nullable PackageManagerInternal mPackageManagerInternal;
+
+    /**
+     * Interface for app-op modes.
+     */
+    @VisibleForTesting
+    AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+
+    /**
+     * Interface for app-op restrictions.
+     */
+    @VisibleForTesting
+    AppOpsRestrictions mAppOpsRestrictions;
+
+    private AppOpsUidStateTracker mUidStateTracker;
+
+    /**
+     * Hands the definition of foreground and uid states
+     */
+    @GuardedBy("this")
+    public AppOpsUidStateTracker getUidStateTracker() {
+        if (mUidStateTracker == null) {
+            mUidStateTracker = new AppOpsUidStateTrackerImpl(
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsServiceImpl.this) {
+                            r.run();
+                        }
+                    },
+                    Clock.SYSTEM_CLOCK, mConstants);
+
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
+        }
+        return mUidStateTracker;
+    }
+
+    /**
+     * All times are in milliseconds. These constants are kept synchronized with the system
+     * global Settings. Any access to this class or its fields should be done while
+     * holding the AppOpsService lock.
+     */
+    final class Constants extends ContentObserver {
+
+        /**
+         * How long we want for a drop in uid state from top to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+         */
+        public long TOP_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from foreground to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+         */
+        public long FG_SERVICE_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from background to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+         */
+        public long BG_STATE_SETTLE_TIME;
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+        private ContentResolver mResolver;
+
+        Constants(Handler handler) {
+            super(handler);
+            updateConstants();
+        }
+
+        public void startMonitoring(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+                    false, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            String value = mResolver != null ? Settings.Global.getString(mResolver,
+                    Settings.Global.APP_OPS_CONSTANTS) : "";
+
+            synchronized (AppOpsServiceImpl.this) {
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad app ops settings", e);
+                }
+                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+            }
+        }
+
+        void dump(PrintWriter pw) {
+            pw.println("  Settings:");
+
+            pw.print("    ");
+            pw.print(KEY_TOP_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    ");
+            pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    ");
+            pw.print(KEY_BG_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+            pw.println();
+        }
+    }
+
+    @VisibleForTesting
+    final Constants mConstants;
+
+    @VisibleForTesting
+    final class UidState {
+        public final int uid;
+
+        public ArrayMap<String, Ops> pkgOps;
+
+        // true indicates there is an interested observer, false there isn't but it has such an op
+        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+        public SparseBooleanArray foregroundOps;
+        public boolean hasForegroundWatchers;
+
+        public UidState(int uid) {
+            this.uid = uid;
+        }
+
+        public void clear() {
+            mAppOpsServiceInterface.removeUid(uid);
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+                }
+            }
+            pkgOps = null;
+        }
+
+        public boolean isDefault() {
+            boolean areAllPackageModesDefault = true;
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
+                            UserHandle.getUserId(uid))) {
+                        areAllPackageModesDefault = false;
+                        break;
+                    }
+                }
+            }
+            return (pkgOps == null || pkgOps.isEmpty())
+                    && mAppOpsServiceInterface.areUidModesDefault(uid)
+                    && areAllPackageModesDefault;
+        }
+
+        // Functions for uid mode access and manipulation.
+        public SparseIntArray getNonDefaultUidModes() {
+            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
+        }
+
+        public int getUidMode(int op) {
+            return mAppOpsServiceInterface.getUidMode(uid, op);
+        }
+
+        public boolean setUidMode(int op, int mode) {
+            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        int evalMode(int op, int mode) {
+            return getUidStateTracker().evalMode(uid, op, mode);
+        }
+
+        public void evalForegroundOps() {
+            foregroundOps = null;
+            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
+            if (pkgOps != null) {
+                for (int i = pkgOps.size() - 1; i >= 0; i--) {
+                    foregroundOps = mAppOpsServiceInterface
+                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
+                                    foregroundOps,
+                                    UserHandle.getUserId(uid));
+                }
+            }
+            hasForegroundWatchers = false;
+            if (foregroundOps != null) {
+                for (int i = 0; i < foregroundOps.size(); i++) {
+                    if (foregroundOps.valueAt(i)) {
+                        hasForegroundWatchers = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public int getState() {
+            return getUidStateTracker().getUidState(uid);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public void dump(PrintWriter pw, long nowElapsed) {
+            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+        }
+    }
+
+    static final class Ops extends SparseArray<Op> {
+        final String packageName;
+        final UidState uidState;
+
+        /**
+         * The restriction properties of the package. If {@code null} it could not have been read
+         * yet and has to be refreshed.
+         */
+        @Nullable RestrictionBypass bypass;
+
+        /** Lazily populated cache of attributionTags of this package */
+        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+        /**
+         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+         * than or equal to {@link #knownAttributionTags}.
+         */
+        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+        Ops(String _packageName, UidState _uidState) {
+            packageName = _packageName;
+            uidState = _uidState;
+        }
+    }
+
+    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+    private static final class PackageVerificationResult {
+
+        final RestrictionBypass bypass;
+        final boolean isAttributionTagValid;
+
+        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+            this.bypass = bypass;
+            this.isAttributionTagValid = isAttributionTagValid;
+        }
+    }
+
+    final class Op {
+        int op;
+        int uid;
+        final UidState uidState;
+        final @NonNull String packageName;
+
+        /** attributionTag -> AttributedOp */
+        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+        Op(UidState uidState, String packageName, int op, int uid) {
+            this.op = op;
+            this.uid = uid;
+            this.uidState = uidState;
+            this.packageName = packageName;
+        }
+
+        @Mode int getMode() {
+            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void setMode(@Mode int mode) {
+            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void removeAttributionsWithNoTime() {
+            for (int i = mAttributions.size() - 1; i >= 0; i--) {
+                if (!mAttributions.valueAt(i).hasAnyTime()) {
+                    mAttributions.removeAt(i);
+                }
+            }
+        }
+
+        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+                @Nullable String attributionTag) {
+            AttributedOp attributedOp;
+
+            attributedOp = mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
+                        parent);
+                mAttributions.put(attributionTag, attributedOp);
+            }
+
+            return attributedOp;
+        }
+
+        @NonNull
+        OpEntry createEntryLocked() {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+                    new ArrayMap<>(numAttributions);
+            for (int i = 0; i < numAttributions; i++) {
+                attributionEntries.put(mAttributions.keyAt(i),
+                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        @NonNull
+        OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+            for (int i = 0; i < numAttributions; i++) {
+                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+                    attributionEntries.put(mAttributions.keyAt(i),
+                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
+                    break;
+                }
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        boolean isRunning() {
+            final int numAttributions = mAttributions.size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (mAttributions.valueAt(i).isRunning()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
+
+    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
+        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+        public static final int ALL_OPS = -2;
+
+        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+        // Otherwise we can just use the IBinder object.
+        private final IAppOpsCallback mCallback;
+
+        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+                int callingUid, int callingPid) {
+            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+            this.mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ModeCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, getWatchingUid());
+            sb.append(" flags=0x");
+            sb.append(Integer.toHexString(getFlags()));
+            switch (getWatchedOpCode()) {
+                case OP_NONE:
+                    break;
+                case ALL_OPS:
+                    sb.append(" op=(all)");
+                    break;
+                default:
+                    sb.append(" op=");
+                    sb.append(opToName(getWatchedOpCode()));
+                    break;
+            }
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, getCallingUid());
+            sb.append(" pid=");
+            sb.append(getCallingPid());
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingMode(mCallback);
+        }
+
+        @Override
+        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+            mCallback.opChanged(op, uid, packageName);
+        }
+    }
+
+    final class ActiveCallback implements DeathRecipient {
+        final IAppOpsActiveCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ActiveCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingActive(mCallback);
+        }
+    }
+
+    final class StartedCallback implements DeathRecipient {
+        final IAppOpsStartedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("StartedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingStarted(mCallback);
+        }
+    }
+
+    final class NotedCallback implements DeathRecipient {
+        final IAppOpsNotedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("NotedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingNoted(mCallback);
+        }
+    }
+
+    /**
+     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+     */
+    static void onClientDeath(@NonNull AttributedOp attributedOp,
+            @NonNull IBinder clientId) {
+        attributedOp.onClientDeath(clientId);
+    }
+
+    AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
+        mContext = context;
+
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            mSwitchedOps.put(switchCode,
+                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+        }
+        mAppOpsServiceInterface =
+                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+                mAppOpsServiceInterface);
+
+        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+        mFile = new AtomicFile(storagePath, "appops");
+
+        mHandler = handler;
+        mConstants = new Constants(mHandler);
+        readState();
+    }
+
+    /**
+     * Handler for work when packages are removed or updated
+     */
+    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+                synchronized (AppOpsServiceImpl.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
+                    Ops removedOps = uidState.pkgOps.remove(pkgName);
+                    if (removedOps != null) {
+                        scheduleFastWriteLocked();
+                    }
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+                if (pkg == null) {
+                    return;
+                }
+
+                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+                ArraySet<String> attributionTags = new ArraySet<>();
+                attributionTags.add(null);
+                if (pkg.getAttributions() != null) {
+                    int numAttributions = pkg.getAttributions().size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+                        attributionTags.add(attribution.getTag());
+
+                        int numInheritFrom = attribution.getInheritFrom().size();
+                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+                                inheritFromNum++) {
+                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+                                    attribution.getTag());
+                        }
+                    }
+                }
+
+                synchronized (AppOpsServiceImpl.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+
+                    Ops ops = uidState.pkgOps.get(pkgName);
+                    if (ops == null) {
+                        return;
+                    }
+
+                    // Reset cached package properties to re-initialize when needed
+                    ops.bypass = null;
+                    ops.knownAttributionTags.clear();
+
+                    // Merge data collected for removed attributions into their successor
+                    // attributions
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+                                attributionNum--) {
+                            String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+                            if (attributionTags.contains(attributionTag)) {
+                                // attribution still exist after upgrade
+                                continue;
+                            }
+
+                            String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+                                    newAttributionTag);
+                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+                            op.mAttributions.removeAt(attributionNum);
+
+                            scheduleFastWriteLocked();
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    @Override
+    public void systemReady() {
+        mConstants.startMonitoring(mContext.getContentResolver());
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+        IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageUpdateFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+                packageUpdateFilter, null, null);
+
+        synchronized (this) {
+            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+                int uid = mUidStates.keyAt(uidNum);
+                UidState uidState = mUidStates.valueAt(uidNum);
+
+                String[] pkgsInUid = getPackagesForUid(uidState.uid);
+                if (ArrayUtils.isEmpty(pkgsInUid)) {
+                    uidState.clear();
+                    mUidStates.removeAt(uidNum);
+                    scheduleFastWriteLocked();
+                    continue;
+                }
+
+                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+                if (pkgs == null) {
+                    continue;
+                }
+
+                int numPkgs = pkgs.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    String pkg = pkgs.keyAt(pkgNum);
+
+                    String action;
+                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+                        action = Intent.ACTION_PACKAGE_REMOVED;
+                    } else {
+                        action = Intent.ACTION_PACKAGE_REPLACED;
+                    }
+
+                    SystemServerInitThreadPool.submit(
+                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+                                    .setData(Uri.fromParts("package", pkg, null))
+                                    .putExtra(Intent.EXTRA_UID, uid)),
+                            "Update app-ops uidState in case package " + pkg + " changed");
+                }
+            }
+        }
+
+        final IntentFilter packageSuspendFilter = new IntentFilter();
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                final String[] changedPkgs = intent.getStringArrayExtra(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+                    synchronized (AppOpsServiceImpl.this) {
+                        onModeChangedListeners =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (onModeChangedListeners == null) {
+                            continue;
+                        }
+                    }
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
+                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+                    }
+                }
+            }
+        }, UserHandle.ALL, packageSuspendFilter, null, null);
+    }
+
+    @Override
+    public void packageRemoved(int uid, String packageName) {
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null) {
+                return;
+            }
+
+            Ops removedOps = null;
+
+            // Remove any package state if such.
+            if (uidState.pkgOps != null) {
+                removedOps = uidState.pkgOps.remove(packageName);
+                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+            }
+
+            // If we just nuked the last package state check if the UID is valid.
+            if (removedOps != null && uidState.pkgOps.isEmpty()
+                    && getPackagesForUid(uid).length <= 0) {
+                uidState.clear();
+                mUidStates.remove(uid);
+            }
+
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+
+                final int numOps = removedOps.size();
+                for (int opNum = 0; opNum < numOps; opNum++) {
+                    final Op op = removedOps.valueAt(opNum);
+
+                    final int numAttributions = op.mAttributions.size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+                        while (attributedOp.isRunning()) {
+                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+                        }
+                        while (attributedOp.isPaused()) {
+                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+                        }
+                    }
+                }
+            }
+        }
+
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+                mHistoricalRegistry, uid, packageName));
+    }
+
+    @Override
+    public void uidRemoved(int uid) {
+        synchronized (this) {
+            if (mUidStates.indexOfKey(uid) >= 0) {
+                mUidStates.get(uid).clear();
+                mUidStates.remove(uid);
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    // The callback method from ForegroundPolicyInterface
+    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, true);
+
+            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+                    if (!uidState.foregroundOps.valueAt(fgi)) {
+                        continue;
+                    }
+                    final int code = uidState.foregroundOps.keyAt(fgi);
+
+                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
+                                this, code, uidState.uid, true, null));
+                    } else if (uidState.pkgOps != null) {
+                        final ArraySet<OnOpModeChangedListener> listenerSet =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (listenerSet != null) {
+                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+                                if ((listener.getFlags()
+                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+                                        || !listener.isWatchingUid(uidState.uid)) {
+                                    continue;
+                                }
+                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+                                    if (op == null) {
+                                        continue;
+                                    }
+                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+                                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                                AppOpsServiceImpl::notifyOpChanged,
+                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
+                                                uidState.pkgOps.keyAt(pkgi)));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (uidState != null && uidState.pkgOps != null) {
+                int numPkgs = uidState.pkgOps.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = 0; attributionNum < numAttributions;
+                                attributionNum++) {
+                            AttributedOp attributedOp = op.mAttributions.valueAt(
+                                    attributionNum);
+
+                            attributedOp.onUidStateChanged(state);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify the proc state or capability has changed for a certain UID.
+     */
+    @Override
+    public void updateUidProcState(int uid, int procState,
+            @ActivityManager.ProcessCapability int capability) {
+        synchronized (this) {
+            getUidStateTracker().updateUidProcState(uid, procState, capability);
+            if (!mUidStates.contains(uid)) {
+                UidState uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+                onUidStateChanged(uid,
+                        AppOpsUidStateTracker.processStateToUidState(procState), false);
+            }
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        Slog.w(TAG, "Writing app ops before shutdown...");
+        boolean doWrite = false;
+        synchronized (this) {
+            if (mWriteScheduled) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                mHandler.removeCallbacks(mWriteRunner);
+                doWrite = true;
+            }
+        }
+        if (doWrite) {
+            writeState();
+        }
+
+        mHistoricalRegistry.shutdown();
+    }
+
+    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int j = 0; j < pkgOps.size(); j++) {
+                Op curOp = pkgOps.valueAt(j);
+                resOps.add(getOpEntryForResult(curOp));
+            }
+        } else {
+            for (int j = 0; j < ops.length; j++) {
+                Op curOp = pkgOps.get(ops[j]);
+                if (curOp != null) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(getOpEntryForResult(curOp));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    @Nullable
+    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+            @Nullable int[] ops) {
+        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+        if (opModes == null) {
+            return null;
+        }
+
+        int opModeCount = opModes.size();
+        if (opModeCount == 0) {
+            return null;
+        }
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int i = 0; i < opModeCount; i++) {
+                int code = opModes.keyAt(i);
+                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+            }
+        } else {
+            for (int j = 0; j < ops.length; j++) {
+                int code = ops[j];
+                if (opModes.indexOfKey(code) >= 0) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+        return op.createEntryLocked();
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean hasAllPackageAccess = mContext.checkPermission(
+                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+        ArrayList<AppOpsManager.PackageOps> res = null;
+        synchronized (this) {
+            final int uidStateCount = mUidStates.size();
+            for (int i = 0; i < uidStateCount; i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+                    continue;
+                }
+                ArrayMap<String, Ops> packages = uidState.pkgOps;
+                final int packageCount = packages.size();
+                for (int j = 0; j < packageCount; j++) {
+                    Ops pkgOps = packages.valueAt(j);
+                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+                    if (resOps != null) {
+                        if (res == null) {
+                            res = new ArrayList<>();
+                        }
+                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
+                        // Caller can always see their packages and with a permission all.
+                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+                            res.add(resPackage);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops) {
+        enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return Collections.emptyList();
+        }
+        synchronized (this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+                    /* edit */ false);
+            if (pkgOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        // We get to access everything
+        if (callingUid == Process.myPid()) {
+            return;
+        }
+        // Apps can access their own data
+        if (uid == callingUid && packageName != null
+                && checkPackage(uid, packageName) == MODE_ALLOWED) {
+            return;
+        }
+        // Otherwise, you need a permission...
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), callingUid, null);
+    }
+
+    /**
+     * Verify that historical appop request arguments are valid.
+     */
+    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags) {
+        if ((filter & FILTER_BY_UID) != 0) {
+            Preconditions.checkArgument(uid != Process.INVALID_UID);
+        } else {
+            Preconditions.checkArgument(uid == Process.INVALID_UID);
+        }
+
+        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+            Objects.requireNonNull(packageName);
+        } else {
+            Preconditions.checkArgument(packageName == null);
+        }
+
+        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+            Preconditions.checkArgument(attributionTag == null);
+        }
+
+        if ((filter & FILTER_BY_OP_NAMES) != 0) {
+            Objects.requireNonNull(opNames);
+        } else {
+            Preconditions.checkArgument(opNames == null);
+        }
+
+        Preconditions.checkFlagsArgument(filter,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+                        | FILTER_BY_OP_NAMES);
+        Preconditions.checkArgumentNonnegative(beginTimeMillis);
+        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+    }
+
+    @Override
+    public void getHistoricalOps(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
+        PackageManager pm = mContext.getPackageManager();
+
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+        if (!isSelfRequest) {
+            boolean isCallerInstrumented =
+                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+            boolean isCallerPermissionController;
+            try {
+                isCallerPermissionController = pm.getPackageUidAsUser(
+                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+                        UserHandle.getUserId(Binder.getCallingUid()))
+                        == Binder.getCallingUid();
+            } catch (PackageManager.NameNotFoundException doesNotHappen) {
+                return;
+            }
+
+            boolean doesCallerHavePermission = mContext.checkPermission(
+                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid())
+                    == PackageManager.PERMISSION_GRANTED;
+
+            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+                    && !doesCallerHavePermission) {
+                mHandler.post(() -> callback.sendResult(new Bundle()));
+                return;
+            }
+
+            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+        }
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
+    }
+
+    @Override
+    public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
+    }
+
+    @Override
+    public void reloadNonHistoricalState() {
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+        writeState();
+        readState();
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    null, uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void pruneOpLocked(Op op, int uid, String packageName) {
+        op.removeAttributionsWithNoTime();
+
+        if (op.mAttributions.isEmpty()) {
+            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+            if (ops != null) {
+                ops.remove(op.op);
+                op.setMode(AppOpsManager.opToDefaultMode(op.op));
+                if (ops.size() <= 0) {
+                    UidState uidState = ops.uidState;
+                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+                    if (pkgOps != null) {
+                        pkgOps.remove(ops.packageName);
+                        mAppOpsServiceInterface.removePackage(ops.packageName,
+                                UserHandle.getUserId(uidState.uid));
+                        if (pkgOps.isEmpty()) {
+                            uidState.pkgOps = null;
+                        }
+                        if (uidState.isDefault()) {
+                            uidState.clear();
+                            mUidStates.remove(uid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+        if (callingPid == Process.myPid()) {
+            return;
+        }
+        final int callingUser = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+                    // Profile owners are allowed to change modes but only for apps
+                    // within their user.
+                    return;
+                }
+            }
+        }
+        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    @Override
+    public void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+                    + " by uid " + Binder.getCallingUid());
+        }
+
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        code = AppOpsManager.opToSwitch(code);
+
+        if (permissionPolicyCallback == null) {
+            updatePermissionRevokedCompat(uid, code, mode);
+        }
+
+        int previousMode;
+        synchronized (this) {
+            final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                if (mode == defaultMode) {
+                    return;
+                }
+                uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+            }
+            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                previousMode = uidState.getUidMode(code);
+            } else {
+                // doesn't look right but is legacy behavior.
+                previousMode = MODE_DEFAULT;
+            }
+
+            if (!uidState.setUidMode(code, mode)) {
+                return;
+            }
+            uidState.evalForegroundOps();
+            if (mode != MODE_ERRORED && mode != previousMode) {
+                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+            }
+        }
+
+        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+        notifyOpChangedSync(code, uid, null, mode, previousMode);
+    }
+
+    /**
+     * Notify that an op changed for all packages in an uid.
+     *
+     * @param code           The op that changed
+     * @param uid            The uid the op was changed for
+     * @param onlyForeground Only notify watchers that watch for foreground changes
+     */
+    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable IAppOpsCallback callbackToIgnore) {
+        ModeCallback listenerToIgnore = callbackToIgnore != null
+                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+                listenerToIgnore);
+    }
+
+    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            // This can only happen during early boot. At this time the permission state and appop
+            // state are in sync
+            return;
+        }
+
+        String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return;
+        }
+        String packageName = packageNames[0];
+
+        int[] ops = mSwitchedOps.get(switchCode);
+        for (int code : ops) {
+            String permissionName = AppOpsManager.opToPermission(code);
+            if (permissionName == null) {
+                continue;
+            }
+
+            if (packageManager.checkPermission(permissionName, packageName)
+                    != PackageManager.PERMISSION_GRANTED) {
+                continue;
+            }
+
+            PermissionInfo permissionInfo;
+            try {
+                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+                continue;
+            }
+
+            if (!permissionInfo.isRuntime()) {
+                continue;
+            }
+
+            boolean supportsRuntimePermissions = getPackageManagerInternal()
+                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+            UserHandle user = UserHandle.getUserHandleForUid(uid);
+            boolean isRevokedCompat;
+            if (permissionInfo.backgroundPermission != null) {
+                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                                + " permission state, this is discouraged and you should revoke the"
+                                + " runtime permission instead: uid=" + uid + ", switchCode="
+                                + switchCode + ", mode=" + mode + ", permission="
+                                + permissionInfo.backgroundPermission);
+                    }
+
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                isBackgroundRevokedCompat
+                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+                        && mode != AppOpsManager.MODE_FOREGROUND;
+            } else {
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+            }
+
+            if (isRevokedCompat && supportsRuntimePermissions) {
+                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                        + " permission state, this is discouraged and you should revoke the"
+                        + " runtime permission instead: uid=" + uid + ", switchCode="
+                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                packageManager.updatePermissionFlags(permissionName, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+            int previousMode) {
+        final StorageManagerInternal storageManagerInternal =
+                LocalServices.getService(StorageManagerInternal.class);
+        if (storageManagerInternal != null) {
+            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+        }
+    }
+
+    @Override
+    public void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        ArraySet<OnOpModeChangedListener> repCbs = null;
+        code = AppOpsManager.opToSwitch(code);
+
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot setMode", e);
+            return;
+        }
+
+        int previousMode = MODE_DEFAULT;
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+            if (op != null) {
+                if (op.getMode() != mode) {
+                    previousMode = op.getMode();
+                    op.setMode(mode);
+
+                    if (uidState != null) {
+                        uidState.evalForegroundOps();
+                    }
+                    ArraySet<OnOpModeChangedListener> cbs =
+                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    if (repCbs != null && permissionPolicyCallback != null) {
+                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+                    }
+                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+                        // If going into the default mode, prune this op
+                        // if there is nothing else interesting in it.
+                        pruneOpLocked(op, uid, packageName);
+                    }
+                    scheduleFastWriteLocked();
+                    if (mode != MODE_ERRORED) {
+                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+                    }
+                }
+            }
+        }
+        if (repCbs != null) {
+            mHandler.sendMessage(PooledLambda.obtainMessage(
+                    AppOpsServiceImpl::notifyOpChanged,
+                    this, repCbs, code, uid, packageName));
+        }
+
+        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+    }
+
+    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+            int uid, String packageName) {
+        for (int i = 0; i < callbacks.size(); i++) {
+            final OnOpModeChangedListener callback = callbacks.valueAt(i);
+            notifyOpChanged(callback, code, uid, packageName);
+        }
+    }
+
+    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+            int uid, String packageName) {
+        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
+    }
+
+    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+            int op, int uid, String packageName, int previousMode) {
+        boolean duplicate = false;
+        if (reports == null) {
+            reports = new ArrayList<>();
+        } else {
+            final int reportCount = reports.size();
+            for (int j = 0; j < reportCount; j++) {
+                ChangeRec report = reports.get(j);
+                if (report.op == op && report.pkg.equals(packageName)) {
+                    duplicate = true;
+                    break;
+                }
+            }
+        }
+        if (!duplicate) {
+            reports.add(new ChangeRec(op, uid, packageName, previousMode));
+        }
+
+        return reports;
+    }
+
+    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, int previousMode,
+            ArraySet<OnOpModeChangedListener> cbs) {
+        if (cbs == null) {
+            return callbacks;
+        }
+        if (callbacks == null) {
+            callbacks = new HashMap<>();
+        }
+        final int N = cbs.size();
+        for (int i=0; i<N; i++) {
+            OnOpModeChangedListener cb = cbs.valueAt(i);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+            if (changed != reports) {
+                callbacks.put(cb, changed);
+            }
+        }
+        return callbacks;
+    }
+
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+        final int previous_mode;
+
+        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+            previous_mode = _previous_mode;
+        }
+    }
+
+    @Override
+    public void resetAllModes(int reqUserId, String reqPackageName) {
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+                true, true, "resetAllModes", null);
+
+        int reqUid = -1;
+        if (reqPackageName != null) {
+            try {
+                reqUid = AppGlobals.getPackageManager().getPackageUid(
+                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+
+        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+        ArrayList<ChangeRec> allChanges = new ArrayList<>();
+        synchronized (this) {
+            boolean changed = false;
+            for (int i = mUidStates.size() - 1; i >= 0; i--) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+                    final int uidOpCount = opModes.size();
+                    for (int j = uidOpCount - 1; j >= 0; j--) {
+                        final int code = opModes.keyAt(j);
+                        if (AppOpsManager.opAllowsReset(code)) {
+                            int previousMode = opModes.valueAt(j);
+                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+                            for (String packageName : getPackagesForUid(uidState.uid)) {
+                                callbacks = addCallbacks(callbacks, code, uidState.uid,
+                                        packageName, previousMode,
+                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
+                                callbacks = addCallbacks(callbacks, code, uidState.uid,
+                                        packageName, previousMode, mAppOpsServiceInterface
+                                                .getPackageModeChangedListeners(packageName));
+
+                                allChanges = addChange(allChanges, code, uidState.uid,
+                                        packageName, previousMode);
+                            }
+                        }
+                    }
+                }
+
+                if (uidState.pkgOps == null) {
+                    continue;
+                }
+
+                if (reqUserId != UserHandle.USER_ALL
+                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
+                    // Skip any ops for a different user
+                    continue;
+                }
+
+                Map<String, Ops> packages = uidState.pkgOps;
+                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                boolean uidChanged = false;
+                while (it.hasNext()) {
+                    Map.Entry<String, Ops> ent = it.next();
+                    String packageName = ent.getKey();
+                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+                        // Skip any ops for a different package
+                        continue;
+                    }
+                    Ops pkgOps = ent.getValue();
+                    for (int j=pkgOps.size()-1; j>=0; j--) {
+                        Op curOp = pkgOps.valueAt(j);
+                        if (shouldDeferResetOpToDpm(curOp.op)) {
+                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+                            continue;
+                        }
+                        if (AppOpsManager.opAllowsReset(curOp.op)
+                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+                            int previousMode = curOp.getMode();
+                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+                            changed = true;
+                            uidChanged = true;
+                            final int uid = curOp.uidState.uid;
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode,
+                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode, mAppOpsServiceInterface
+                                            .getPackageModeChangedListeners(packageName));
+
+                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
+                                    previousMode);
+                            curOp.removeAttributionsWithNoTime();
+                            if (curOp.mAttributions.isEmpty()) {
+                                pkgOps.removeAt(j);
+                            }
+                        }
+                    }
+                    if (pkgOps.size() == 0) {
+                        it.remove();
+                        mAppOpsServiceInterface.removePackage(packageName,
+                                UserHandle.getUserId(uidState.uid));
+                    }
+                }
+                if (uidState.isDefault()) {
+                    uidState.clear();
+                    mUidStates.remove(uidState.uid);
+                }
+                if (uidChanged) {
+                    uidState.evalForegroundOps();
+                }
+            }
+
+            if (changed) {
+                scheduleFastWriteLocked();
+            }
+        }
+        if (callbacks != null) {
+            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+                    : callbacks.entrySet()) {
+                OnOpModeChangedListener cb = ent.getKey();
+                ArrayList<ChangeRec> reports = ent.getValue();
+                for (int i=0; i<reports.size(); i++) {
+                    ChangeRec rep = reports.get(i);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsServiceImpl::notifyOpChanged,
+                            this, cb, rep.op, rep.uid, rep.pkg));
+                }
+            }
+        }
+
+        int numChanges = allChanges.size();
+        for (int i = 0; i < numChanges; i++) {
+            ChangeRec change = allChanges.get(i);
+            notifyOpChangedSync(change.op, change.uid, change.pkg,
+                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+        }
+    }
+
+    private boolean shouldDeferResetOpToDpm(int op) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        return dpmi != null && dpmi.supportsResetOp(op);
+    }
+
+    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        dpmi.resetOp(op, packageName, userId);
+    }
+
+    private void evalAllForegroundOpsLocked() {
+        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+            final UidState uidState = mUidStates.valueAt(uidi);
+            if (uidState.foregroundOps != null) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingModeWithFlags(int op, String packageName, int flags,
+            IAppOpsCallback callback) {
+        int watchedUid = -1;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        // TODO: should have a privileged permission to protect this.
+        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+        // the USAGE_STATS permission since this can provide information about when an
+        // app is in the foreground?
+        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+        if (callback == null) {
+            return;
+        }
+        final boolean mayWatchPackageName = packageName != null
+                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+        synchronized (this) {
+            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+            int notifiedOps;
+            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+                if (op == OP_NONE) {
+                    notifiedOps = ALL_OPS;
+                } else {
+                    notifiedOps = op;
+                }
+            } else {
+                notifiedOps = switchOp;
+            }
+
+            ModeCallback cb = mModeWatchers.get(callback.asBinder());
+            if (cb == null) {
+                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+                        callingPid);
+                mModeWatchers.put(callback.asBinder(), cb);
+            }
+            if (switchOp != AppOpsManager.OP_NONE) {
+                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
+            }
+            if (mayWatchPackageName) {
+                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
+            }
+            evalAllForegroundOpsLocked();
+        }
+    }
+
+    @Override
+    public void stopWatchingMode(IAppOpsCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+            if (cb != null) {
+                cb.unlinkToDeath();
+                mAppOpsServiceInterface.removeListener(cb);
+            }
+
+            evalAllForegroundOpsLocked();
+        }
+    }
+
+    @Override
+    public int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+    }
+
+    /**
+     * Get the mode of an app-op.
+     *
+     * @param code        The code of the op
+     * @param uid         The uid of the package the op belongs to
+     * @param packageName The package the op belongs to
+     * @param raw         If the raw state of eval-ed state should be checked.
+     * @return The mode of the op
+     */
+    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "checkOperation", e);
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        synchronized (this) {
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+                return AppOpsManager.MODE_IGNORED;
+            }
+            code = AppOpsManager.opToSwitch(code);
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState != null
+                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                final int rawMode = uidState.getUidMode(code);
+                return raw ? rawMode : uidState.evalMode(code, rawMode);
+            }
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+            if (op == null) {
+                return AppOpsManager.opToDefaultMode(code);
+            }
+            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+        }
+    }
+
+    @Override
+    public int checkPackage(int uid, String packageName) {
+        Objects.requireNonNull(packageName);
+        try {
+            verifyAndGetBypass(uid, packageName, null);
+            // When the caller is the system, it's possible that the packageName is the special
+            // one (e.g., "root") which isn't actually existed.
+            if (resolveUid(packageName) == uid
+                    || (isPackageExisted(packageName)
+                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+                return AppOpsManager.MODE_ALLOWED;
+            }
+            return AppOpsManager.MODE_ERRORED;
+        } catch (SecurityException ignored) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+    }
+
+    private boolean isPackageExisted(String packageName) {
+        return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
+    }
+
+    /**
+     * This method will check with PackageManager to determine if the package provided should
+     * be visible to the {@link Binder#getCallingUid()}.
+     *
+     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+     */
+    private boolean filterAppAccessUnlocked(String packageName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        return LocalServices.getService(PackageManagerInternal.class)
+                .filterAppAccess(packageName, callingUid, userId);
+    }
+
+    @Override
+    public int noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String message) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+    }
+
+    @Override
+    public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @OpFlags int flags) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "noteOperation", e);
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                if (DEBUG) {
+                    Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + "flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return AppOpsManager.MODE_ERRORED;
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            if (attributedOp.isRunning()) {
+                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+                        + code + " startTime of in progress event="
+                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+            }
+
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+                attributedOp.rejected(uidState.getState(), flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return AppOpsManager.MODE_IGNORED;
+            }
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            uidMode);
+                    return uidMode;
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            mode);
+                    return mode;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "noteOperation: allowing code " + code + " uid " + uid + " package "
+                                + packageName + (attributionTag == null ? ""
+                                : "." + attributionTag) + " flags: "
+                                + AppOpsManager.flagsToString(flags));
+            }
+            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                    AppOpsManager.MODE_ALLOWED);
+            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState.getState(),
+                    flags);
+
+            return AppOpsManager.MODE_ALLOWED;
+        }
+    }
+
+    @Override
+    public boolean isAttributionTagValid(int uid, @NonNull String packageName,
+            @Nullable String attributionTag,
+            @Nullable String proxyPackageName) {
+        try {
+            return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
+                    .isAttributionTagValid;
+        } catch (SecurityException ignored) {
+            // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
+            // when they need the bypass object.
+            return false;
+        }
+    }
+
+    // TODO moltmann: Allow watching for attribution ops
+    @Override
+    public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        if (ops != null) {
+            Preconditions.checkArrayElementsInRange(ops, 0,
+                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+        }
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mActiveWatchers.put(callback.asBinder(), callbacks);
+            }
+            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, activeCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingActive(IAppOpsActiveCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            final SparseArray<ActiveCallback> activeCallbacks =
+                    mActiveWatchers.remove(callback.asBinder());
+            if (activeCallbacks == null) {
+                return;
+            }
+            final int callbackCount = activeCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                activeCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mStartedWatchers.put(callback.asBinder(), callbacks);
+            }
+
+            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, startedCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingStarted(IAppOpsStartedCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            final SparseArray<StartedCallback> startedCallbacks =
+                    mStartedWatchers.remove(callback.asBinder());
+            if (startedCallbacks == null) {
+                return;
+            }
+
+            final int callbackCount = startedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                startedCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mNotedWatchers.put(callback.asBinder(), callbacks);
+            }
+            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, notedCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingNoted(IAppOpsNotedCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            final SparseArray<NotedCallback> notedCallbacks =
+                    mNotedWatchers.remove(callback.asBinder());
+            if (notedCallbacks == null) {
+                return;
+            }
+            final int callbackCount = notedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                notedCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public int startOperation(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, @NonNull String message,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+        // purposes and not as a check, also make sure that the caller is allowed to access
+        // the data gated by OP_RECORD_AUDIO.
+        //
+        // TODO: Revert this change before Android 12.
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return result;
+            }
+        }
+        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+                attributionFlags, attributionChainId, /*dryRun*/ false);
+    }
+
+    private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+        return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+    }
+
+    @Override
+    public int startOperationUnchecked(IBinder clientId, int code, int uid,
+            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+            boolean startIfModeDefault, @AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "startOperation", e);
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        boolean isRestricted;
+        int startType = START_TYPE_FAILED;
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                if (!dryRun) {
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+                            attributionChainId);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + " flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return AppOpsManager.MODE_ERRORED;
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            final UidState uidState = ops.uidState;
+            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+                    false);
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, uidMode, startType, attributionFlags, attributionChainId);
+                    }
+                    return uidMode;
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (!shouldStartForMode(mode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, mode, startType, attributionFlags, attributionChainId);
+                    }
+                    return mode;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                        + " package " + packageName + " restricted: " + isRestricted
+                        + " flags: " + AppOpsManager.flagsToString(flags));
+            }
+            if (!dryRun) {
+                try {
+                    if (isRestricted) {
+                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                    } else {
+                        attributedOp.started(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                        startType = START_TYPE_STARTED;
+                    }
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+                        attributionChainId);
+            }
+        }
+
+        // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
+        return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
+    }
+
+    @Override
+    public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return;
+        }
+
+        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+    }
+
+    @Override
+    public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot finishOperation", e);
+            return;
+        }
+
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+                    pvr.bypass, /* edit */ true);
+            if (op == null) {
+                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+
+            if (attributedOp.isRunning() || attributedOp.isPaused()) {
+                attributedOp.finished(clientId);
+            } else {
+                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+            }
+        }
+    }
+
+    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean active,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<ActiveCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mActiveWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+            ActiveCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpActiveChanged,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+                attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+        // There are features watching for mode changes such as window manager
+        // and location manager which are in our process. The callbacks in these
+        // features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final ActiveCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+                            active, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+            String attributionTag, @OpFlags int flags, @Mode int result,
+            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<StartedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mStartedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+            StartedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpStarted,
+                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+                result, startedType, attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final StartedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+                            result, startedType, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+            String attributionTag, @OpFlags int flags, @Mode int result) {
+        ArraySet<NotedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mNotedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+            final NotedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpChecked,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+                result));
+    }
+
+    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result) {
+        // There are features watching for checks in our process. The callbacks in
+        // these features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final NotedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+                            result);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void verifyIncomingUid(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void verifyIncomingOp(int op) {
+        if (op >= 0 && op < AppOpsManager._NUM_OP) {
+            // Enforce manage appops permission if it's a restricted read op.
+            if (opRestrictsRead(op)) {
+                mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                        Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+            }
+            return;
+        }
+        throw new IllegalArgumentException("Bad operation #" + op);
+    }
+
+    private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
+        final int callingUid = Binder.getCallingUid();
+        // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
+        if (packageName == null || isSpecialPackage(callingUid, packageName)) {
+            return true;
+        }
+
+        // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
+        // the end. Although that exception would be caught and return, we could make it return
+        // early.
+        if (!isPackageExisted(packageName)) {
+            return false;
+        }
+
+        if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
+            Slog.w(TAG, packageName + " not found from " + callingUid);
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
+        final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
+        return callingUid == Process.SYSTEM_UID
+                || resolveUid(resolvedPackage) != Process.INVALID_UID;
+    }
+
+    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState = new UidState(uid);
+            mUidStates.put(uid, uidState);
+        }
+
+        return uidState;
+    }
+
+    @Override
+    public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+        synchronized (this) {
+            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+        }
+    }
+
+    /**
+     * @return {@link PackageManagerInternal}
+     */
+    private @NonNull PackageManagerInternal getPackageManagerInternal() {
+        if (mPackageManagerInternal == null) {
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        return mPackageManagerInternal;
+    }
+
+    @Override
+    public void verifyPackage(int uid, String packageName) {
+        verifyAndGetBypass(uid, packageName, null);
+    }
+
+    /**
+     * Create a restriction description matching the properties of the package.
+     *
+     * @param pkg The package to create the restriction description for
+     * @return The restriction matching the package
+     */
+    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+                mContext.checkPermission(android.Manifest.permission
+                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+                        == PackageManager.PERMISSION_GRANTED);
+    }
+
+    /**
+     * @see #verifyAndGetBypass(int, String, String, String)
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag) {
+        return verifyAndGetBypass(uid, packageName, attributionTag, null);
+    }
+
+    /**
+     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+     * description} for the package, along with a boolean indicating whether the attribution tag is
+     * valid.
+     *
+     * @param uid              The uid the package belongs to
+     * @param packageName      The package the might belong to the uid
+     * @param attributionTag   attribution tag or {@code null} if no need to verify
+     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+     * attribution tag is valid
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName) {
+        if (uid == Process.ROOT_UID) {
+            // For backwards compatibility, don't check package name for root UID.
+            return new PackageVerificationResult(null,
+                    /* isAttributionTagValid */ true);
+        }
+        if (Process.isSdkSandboxUid(uid)) {
+            // SDK sandbox processes run in their own UID range, but their associated
+            // UID for checks should always be the UID of the package implementing SDK sandbox
+            // service.
+            // TODO: We will need to modify the callers of this function instead, so
+            // modifications and checks against the app ops state are done with the
+            // correct UID.
+            try {
+                final PackageManager pm = mContext.getPackageManager();
+                final String supplementalPackageName = pm.getSdkSandboxPackageName();
+                if (Objects.equals(packageName, supplementalPackageName)) {
+                    uid = pm.getPackageUidAsUser(supplementalPackageName,
+                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen for the supplemental package
+                e.printStackTrace();
+            }
+        }
+
+
+        // Do not check if uid/packageName/attributionTag is already known.
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState != null && uidState.pkgOps != null) {
+                Ops ops = uidState.pkgOps.get(packageName);
+
+                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+                        attributionTag)) && ops.bypass != null) {
+                    return new PackageVerificationResult(ops.bypass,
+                            ops.validAttributionTags.contains(attributionTag));
+                }
+            }
+        }
+
+        int callingUid = Binder.getCallingUid();
+
+        // Allow any attribution tag for resolvable uids
+        int pkgUid;
+        if (Objects.equals(packageName, "com.android.shell")) {
+            // Special case for the shell which is a package but should be able
+            // to bypass app attribution tag restrictions.
+            pkgUid = Process.SHELL_UID;
+        } else {
+            pkgUid = resolveUid(packageName);
+        }
+        if (pkgUid != Process.INVALID_UID) {
+            if (pkgUid != UserHandle.getAppId(uid)) {
+                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+                        + UserHandle.getAppId(uid) + otherUidMessage);
+            }
+            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+                    /* isAttributionTagValid */ true);
+        }
+
+        int userId = UserHandle.getUserId(uid);
+        RestrictionBypass bypass = null;
+        boolean isAttributionTagValid = false;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+            AndroidPackage pkg = pmInt.getPackage(packageName);
+            if (pkg != null) {
+                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+                bypass = getBypassforPackage(pkg);
+            }
+            if (!isAttributionTagValid) {
+                AndroidPackage proxyPkg = proxyPackageName != null
+                        ? pmInt.getPackage(proxyPackageName) : null;
+                // Re-check in proxy.
+                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+                String msg;
+                if (pkg != null && isAttributionTagValid) {
+                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+                            + " package " + proxyPackageName + ", this is not advised";
+                } else if (pkg != null) {
+                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
+                            + packageName;
+                } else {
+                    msg = "package " + packageName + " not found, can't check for "
+                            + "attributionTag " + attributionTag;
+                }
+
+                try {
+                    if (!mPlatformCompat.isChangeEnabledByPackageName(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+                            userId) || !mPlatformCompat.isChangeEnabledByUid(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+                            callingUid)) {
+                        // Do not override tags if overriding is not enabled for this package
+                        isAttributionTagValid = true;
+                    }
+                    Slog.e(TAG, msg);
+                } catch (RemoteException neverHappens) {
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        if (pkgUid != uid) {
+            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+                    + otherUidMessage);
+        }
+
+        return new PackageVerificationResult(bypass, isAttributionTagValid);
+    }
+
+    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+            @Nullable String attributionTag) {
+        if (pkg == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        if (pkg.getAttributions() != null) {
+            int numAttributions = pkg.getAttributions().size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get (and potentially create) ops.
+     *
+     * @param uid                   The uid the package belongs to
+     * @param packageName           The name of the package
+     * @param attributionTag        attribution tag
+     * @param isAttributionTagValid whether the given attribution tag is valid
+     * @param bypass                When to bypass certain op restrictions (can be null if edit
+        *                              == false)
+     * @param edit                  If an ops does not exist, create the ops?
+     * @return The ops
+     */
+    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+        UidState uidState = getUidStateLocked(uid, edit);
+        if (uidState == null) {
+            return null;
+        }
+
+        if (uidState.pkgOps == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState.pkgOps = new ArrayMap<>();
+        }
+
+        Ops ops = uidState.pkgOps.get(packageName);
+        if (ops == null) {
+            if (!edit) {
+                return null;
+            }
+            ops = new Ops(packageName, uidState);
+            uidState.pkgOps.put(packageName, ops);
+        }
+
+        if (edit) {
+            if (bypass != null) {
+                ops.bypass = bypass;
+            }
+
+            if (attributionTag != null) {
+                ops.knownAttributionTags.add(attributionTag);
+                if (isAttributionTagValid) {
+                    ops.validAttributionTags.add(attributionTag);
+                } else {
+                    ops.validAttributionTags.remove(attributionTag);
+                }
+            }
+        }
+
+        return ops;
+    }
+
+    @Override
+    public void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    @Override
+    public void scheduleFastWriteLocked() {
+        if (!mFastWriteScheduled) {
+            mWriteScheduled = true;
+            mFastWriteScheduled = true;
+            mHandler.removeCallbacks(mWriteRunner);
+            mHandler.postDelayed(mWriteRunner, 10 * 1000);
+        }
+    }
+
+    /**
+     * Get the state of an op for a uid.
+     *
+     * @param code                  The code of the op
+     * @param uid                   The uid the of the package
+     * @param packageName           The package name for which to get the state for
+     * @param attributionTag        The attribution tag
+     * @param isAttributionTagValid Whether the given attribution tag is valid
+     * @param bypass                When to bypass certain op restrictions (can be null if edit
+     *                              == false)
+     * @param edit                  Iff {@code true} create the {@link Op} object if not yet created
+     * @return The {@link Op state} of the op
+     */
+    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean isAttributionTagValid,
+            @Nullable RestrictionBypass bypass, boolean edit) {
+        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+                edit);
+        if (ops == null) {
+            return null;
+        }
+        return getOpLocked(ops, code, uid, edit);
+    }
+
+    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+        Op op = ops.get(code);
+        if (op == null) {
+            if (!edit) {
+                return null;
+            }
+            op = new Op(ops.uidState, ops.packageName, code, uid);
+            ops.put(code, op);
+        }
+        if (edit) {
+            scheduleWriteLocked();
+        }
+        return op;
+    }
+
+    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+    }
+
+    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+        int restrictionSetCount = mOpGlobalRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code)) {
+                return true;
+            }
+        }
+
+        int userHandle = UserHandle.getUserId(uid);
+        restrictionSetCount = mOpUserRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            // For each client, check that the given op is not restricted, or that the given
+            // package is exempt from the restriction.
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+                    isCheckOp)) {
+                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+                if (opBypass != null) {
+                    // If we are the system, bypass user restrictions for certain codes
+                    synchronized (this) {
+                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+                            return false;
+                        }
+                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+                            return false;
+                        }
+                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+                                && appBypass.isRecordAudioRestrictionExcept) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void readState() {
+        int oldVersion = NO_VERSION;
+        synchronized (mFile) {
+            synchronized (this) {
+                FileInputStream stream;
+                try {
+                    stream = mFile.openRead();
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    return;
+                }
+                boolean success = false;
+                mUidStates.clear();
+                mAppOpsServiceInterface.clearAllModes();
+                try {
+                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.START_TAG
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        // Parse next until we reach the start or end
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new IllegalStateException("no start tag found");
+                    }
+
+                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+
+                    int outerDepth = parser.getDepth();
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String tagName = parser.getName();
+                        if (tagName.equals("pkg")) {
+                            readPackage(parser);
+                        } else if (tagName.equals("uid")) {
+                            readUidOps(parser);
+                        } else {
+                            Slog.w(TAG, "Unknown element under <app-ops>: "
+                                    + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+                    success = true;
+                } catch (IllegalStateException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NullPointerException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (XmlPullParserException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IndexOutOfBoundsException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } finally {
+                    if (!success) {
+                        mUidStates.clear();
+                        mAppOpsServiceInterface.clearAllModes();
+                    }
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        synchronized (this) {
+            upgradeLocked(oldVersion);
+        }
+    }
+
+    private void upgradeRunAnyInBackgroundLocked() {
+        for (int i = 0; i < mUidStates.size(); i++) {
+            final UidState uidState = mUidStates.valueAt(i);
+            if (uidState == null) {
+                continue;
+            }
+            SparseIntArray opModes = uidState.getNonDefaultUidModes();
+            if (opModes != null) {
+                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                if (idx >= 0) {
+                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+                            opModes.valueAt(idx));
+                }
+            }
+            if (uidState.pkgOps == null) {
+                continue;
+            }
+            boolean changed = false;
+            for (int j = 0; j < uidState.pkgOps.size(); j++) {
+                Ops ops = uidState.pkgOps.valueAt(j);
+                if (ops != null) {
+                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+                        final Op copy = new Op(op.uidState, op.packageName,
+                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+                        copy.setMode(op.getMode());
+                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+                        changed = true;
+                    }
+                }
+            }
+            if (changed) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    private void upgradeLocked(int oldVersion) {
+        if (oldVersion >= CURRENT_VERSION) {
+            return;
+        }
+        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+        switch (oldVersion) {
+            case NO_VERSION:
+                upgradeRunAnyInBackgroundLocked();
+                // fall through
+            case 1:
+                // for future upgrades
+        }
+        scheduleFastWriteLocked();
+    }
+
+    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+                setUidMode(code, uid, mode, null);
+            } else {
+                Slog.w(TAG, "Unknown element under <uid-ops>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readPackage(TypedXmlPullParser parser)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readUid(parser, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUid(TypedXmlPullParser parser, String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int uid = parser.getAttributeInt(null, "n");
+        final UidState uidState = getUidStateLocked(uid, true);
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, uidState, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        uidState.evalForegroundOps();
+    }
+
+    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+            @Nullable String attribution)
+            throws NumberFormatException, IOException, XmlPullParserException {
+        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+        final long key = parser.getAttributeLong(null, "n");
+        final int uidState = extractUidStateFromKey(key);
+        final int opFlags = extractFlagsFromKey(key);
+
+        final long accessTime = parser.getAttributeLong(null, "t", 0);
+        final long rejectTime = parser.getAttributeLong(null, "r", 0);
+        final long accessDuration = parser.getAttributeLong(null, "d", -1);
+        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+        if (accessTime > 0) {
+            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+                    proxyAttributionTag, uidState, opFlags);
+        }
+        if (rejectTime > 0) {
+            attributedOp.rejected(rejectTime, uidState, opFlags);
+        }
+    }
+
+    private void readOp(TypedXmlPullParser parser,
+            @NonNull UidState uidState, @NonNull String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int opCode = parser.getAttributeInt(null, "n");
+        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+        op.setMode(mode);
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("st")) {
+                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+            } else {
+                Slog.w(TAG, "Unknown element under <op>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        if (uidState.pkgOps == null) {
+            uidState.pkgOps = new ArrayMap<>();
+        }
+        Ops ops = uidState.pkgOps.get(pkgName);
+        if (ops == null) {
+            ops = new Ops(pkgName, uidState);
+            uidState.pkgOps.put(pkgName, ops);
+        }
+        ops.put(op.op, op);
+    }
+
+    @Override
+    public void writeState() {
+        synchronized (mFile) {
+            FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state: " + e);
+                return;
+            }
+
+            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+            try {
+                TypedXmlSerializer out = Xml.resolveSerializer(stream);
+                out.startDocument(null, true);
+                out.startTag(null, "app-ops");
+                out.attributeInt(null, "v", CURRENT_VERSION);
+
+                SparseArray<SparseIntArray> uidStatesClone;
+                synchronized (this) {
+                    uidStatesClone = new SparseArray<>(mUidStates.size());
+
+                    final int uidStateCount = mUidStates.size();
+                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                        UidState uidState = mUidStates.valueAt(uidStateNum);
+                        int uid = mUidStates.keyAt(uidStateNum);
+
+                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                        if (opModes != null && opModes.size() > 0) {
+                            uidStatesClone.put(uid, opModes);
+                        }
+                    }
+                }
+
+                final int uidStateCount = uidStatesClone.size();
+                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+                    if (opModes != null && opModes.size() > 0) {
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+                        final int opCount = opModes.size();
+                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+                            final int op = opModes.keyAt(opCountNum);
+                            final int mode = opModes.valueAt(opCountNum);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op);
+                            out.attributeInt(null, "m", mode);
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                }
+
+                if (allOps != null) {
+                    String lastPkg = null;
+                    for (int i = 0; i < allOps.size(); i++) {
+                        AppOpsManager.PackageOps pkg = allOps.get(i);
+                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+                            if (lastPkg != null) {
+                                out.endTag(null, "pkg");
+                            }
+                            lastPkg = pkg.getPackageName();
+                            if (lastPkg != null) {
+                                out.startTag(null, "pkg");
+                                out.attribute(null, "n", lastPkg);
+                            }
+                        }
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", pkg.getUid());
+                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
+                        for (int j = 0; j < ops.size(); j++) {
+                            AppOpsManager.OpEntry op = ops.get(j);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op.getOp());
+                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+                                out.attributeInt(null, "m", op.getMode());
+                            }
+
+                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+                                final AttributedOpEntry attribution =
+                                        op.getAttributedOpEntries().get(attributionTag);
+
+                                final ArraySet<Long> keys = attribution.collectKeys();
+
+                                final int keyCount = keys.size();
+                                for (int k = 0; k < keyCount; k++) {
+                                    final long key = keys.valueAt(k);
+
+                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
+                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+                                    final long accessTime = attribution.getLastAccessTime(uidState,
+                                            uidState, flags);
+                                    final long rejectTime = attribution.getLastRejectTime(uidState,
+                                            uidState, flags);
+                                    final long accessDuration = attribution.getLastDuration(
+                                            uidState, uidState, flags);
+                                    // Proxy information for rejections is not backed up
+                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+                                            uidState, uidState, flags);
+
+                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+                                            && proxy == null) {
+                                        continue;
+                                    }
+
+                                    String proxyPkg = null;
+                                    String proxyAttributionTag = null;
+                                    int proxyUid = Process.INVALID_UID;
+                                    if (proxy != null) {
+                                        proxyPkg = proxy.getPackageName();
+                                        proxyAttributionTag = proxy.getAttributionTag();
+                                        proxyUid = proxy.getUid();
+                                    }
+
+                                    out.startTag(null, "st");
+                                    if (attributionTag != null) {
+                                        out.attribute(null, "id", attributionTag);
+                                    }
+                                    out.attributeLong(null, "n", key);
+                                    if (accessTime > 0) {
+                                        out.attributeLong(null, "t", accessTime);
+                                    }
+                                    if (rejectTime > 0) {
+                                        out.attributeLong(null, "r", rejectTime);
+                                    }
+                                    if (accessDuration > 0) {
+                                        out.attributeLong(null, "d", accessDuration);
+                                    }
+                                    if (proxyPkg != null) {
+                                        out.attribute(null, "pp", proxyPkg);
+                                    }
+                                    if (proxyAttributionTag != null) {
+                                        out.attribute(null, "pc", proxyAttributionTag);
+                                    }
+                                    if (proxyUid >= 0) {
+                                        out.attributeInt(null, "pu", proxyUid);
+                                    }
+                                    out.endTag(null, "st");
+                                }
+                            }
+
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                    if (lastPkg != null) {
+                        out.endTag(null, "pkg");
+                    }
+                }
+
+                out.endTag(null, "app-ops");
+                out.endDocument();
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state, restoring backup.", e);
+                mFile.failWrite(stream);
+            }
+        }
+        mHistoricalRegistry.writeAndClearDiscreteHistory();
+    }
+
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) dump options:");
+        pw.println("  -h");
+        pw.println("    Print this help text.");
+        pw.println("  --op [OP]");
+        pw.println("    Limit output to data associated with the given app op code.");
+        pw.println("  --mode [MODE]");
+        pw.println("    Limit output to data associated with the given app op mode.");
+        pw.println("  --package [PACKAGE]");
+        pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --attributionTag [attributionTag]");
+        pw.println("    Limit output to data associated with the given attribution tag.");
+        pw.println("  --include-discrete [n]");
+        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
+        pw.println("  --watchers");
+        pw.println("    Only output the watcher sections.");
+        pw.println("  --history");
+        pw.println("    Only output history.");
+        pw.println("  --uid-state-changes");
+        pw.println("    Include logs about uid state changes.");
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+        final int numAttributions = op.mAttributions.size();
+        for (int i = 0; i < numAttributions; i++) {
+            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+                    op.mAttributions.keyAt(i), filterAttributionTag)) {
+                continue;
+            }
+
+            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+                    prefix + "  ");
+            pw.print(prefix + "]\n");
+        }
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+            @NonNull Date date, @NonNull String prefix) {
+
+        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+                attributionTag).getAttributedOpEntries().get(attributionTag);
+
+        final ArraySet<Long> keys = entry.collectKeys();
+
+        final int keyCount = keys.size();
+        for (int k = 0; k < keyCount; k++) {
+            final long key = keys.valueAt(k);
+
+            final int uidState = AppOpsManager.extractUidStateFromKey(key);
+            final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+            String proxyPkg = null;
+            String proxyAttributionTag = null;
+            int proxyUid = Process.INVALID_UID;
+            if (proxy != null) {
+                proxyPkg = proxy.getPackageName();
+                proxyAttributionTag = proxy.getAttributionTag();
+                proxyUid = proxy.getUid();
+            }
+
+            if (accessTime > 0) {
+                pw.print(prefix);
+                pw.print("Access: ");
+                pw.print(AppOpsManager.keyToString(key));
+                pw.print(" ");
+                date.setTime(accessTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(accessTime - now, pw);
+                pw.print(")");
+                if (accessDuration > 0) {
+                    pw.print(" duration=");
+                    TimeUtils.formatDuration(accessDuration, pw);
+                }
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+
+            if (rejectTime > 0) {
+                pw.print(prefix);
+                pw.print("Reject: ");
+                pw.print(AppOpsManager.keyToString(key));
+                date.setTime(rejectTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(rejectTime - now, pw);
+                pw.print(")");
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+        }
+
+        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+        if (attributedOp.isRunning()) {
+            long earliestElapsedTime = Long.MAX_VALUE;
+            long maxNumStarts = 0;
+            int numInProgressEvents = attributedOp.mInProgressEvents.size();
+            for (int i = 0; i < numInProgressEvents; i++) {
+                AttributedOp.InProgressStartOpEvent event =
+                        attributedOp.mInProgressEvents.valueAt(i);
+
+                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+            }
+
+            pw.print(prefix + "Running start at: ");
+            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+            pw.println();
+
+            if (maxNumStarts > 1) {
+                pw.print(prefix + "startNesting=");
+                pw.println(maxNumStarts);
+            }
+        }
+    }
+
+    @NeverCompile // Avoid size overhead of debugging code.
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+        int dumpOp = OP_NONE;
+        String dumpPackage = null;
+        String dumpAttributionTag = null;
+        int dumpUid = Process.INVALID_UID;
+        int dumpMode = -1;
+        boolean dumpWatchers = false;
+        // TODO ntmyren: Remove the dumpHistory and dumpFilter
+        boolean dumpHistory = false;
+        boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
+        int nDiscreteOps = 10;
+        @HistoricalOpsRequestFilter int dumpFilter = 0;
+        boolean dumpAll = false;
+
+        if (args != null) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
+                if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    // dump all data
+                    dumpAll = true;
+                } else if ("--op".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --op option");
+                        return;
+                    }
+                    dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
+                    dumpFilter |= FILTER_BY_OP_NAMES;
+                    if (dumpOp < 0) {
+                        return;
+                    }
+                } else if ("--package".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --package option");
+                        return;
+                    }
+                    dumpPackage = args[i];
+                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
+                    try {
+                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+                                0);
+                    } catch (RemoteException e) {
+                    }
+                    if (dumpUid < 0) {
+                        pw.println("Unknown package: " + dumpPackage);
+                        return;
+                    }
+                    dumpUid = UserHandle.getAppId(dumpUid);
+                    dumpFilter |= FILTER_BY_UID;
+                } else if ("--attributionTag".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --attributionTag option");
+                        return;
+                    }
+                    dumpAttributionTag = args[i];
+                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+                } else if ("--mode".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --mode option");
+                        return;
+                    }
+                    dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
+                    if (dumpMode < 0) {
+                        return;
+                    }
+                } else if ("--watchers".equals(arg)) {
+                    dumpWatchers = true;
+                } else if ("--include-discrete".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --include-discrete option");
+                        return;
+                    }
+                    try {
+                        nDiscreteOps = Integer.valueOf(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Wrong parameter: " + args[i]);
+                        return;
+                    }
+                    includeDiscreteOps = true;
+                } else if ("--history".equals(arg)) {
+                    dumpHistory = true;
+                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+                    pw.println("Unknown option: " + arg);
+                    return;
+                } else if ("--uid-state-changes".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
+                } else {
+                    pw.println("Unknown command: " + arg);
+                    return;
+                }
+            }
+        }
+
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        final Date date = new Date();
+        synchronized (this) {
+            pw.println("Current AppOps Service state:");
+            if (!dumpHistory && !dumpWatchers) {
+                mConstants.dump(pw);
+            }
+            pw.println();
+            final long now = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            boolean needSep = false;
+            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+                    && !dumpHistory) {
+                pw.println("  Profile owners:");
+                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+                    pw.print("    User #");
+                    pw.print(mProfileOwners.keyAt(poi));
+                    pw.print(": ");
+                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+                    pw.println();
+                }
+                pw.println();
+            }
+
+            if (!dumpHistory) {
+                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+            }
+
+            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+                boolean printedHeader = false;
+                for (int i = 0; i < mModeWatchers.size(); i++) {
+                    final ModeCallback cb = mModeWatchers.valueAt(i);
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        pw.println("  All op mode watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+                    pw.print(": ");
+                    pw.println(cb);
+                }
+            }
+            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+                    final SparseArray<ActiveCallback> activeWatchers =
+                            mActiveWatchers.valueAt(watcherNum);
+                    if (activeWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final ActiveCallback cb = activeWatchers.valueAt(0);
+                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op active watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mActiveWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = activeWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+
+                final int watchersSize = mStartedWatchers.size();
+                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+                    final SparseArray<StartedCallback> startedWatchers =
+                            mStartedWatchers.valueAt(watcherNum);
+                    if (startedWatchers.size() <= 0) {
+                        continue;
+                    }
+
+                    final StartedCallback cb = startedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+
+                    if (!printedHeader) {
+                        pw.println("  All op started watchers:");
+                        printedHeader = true;
+                    }
+
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mStartedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+
+                    pw.print("        [");
+                    final int opCount = startedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+
+                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+                    final SparseArray<NotedCallback> notedWatchers =
+                            mNotedWatchers.valueAt(watcherNum);
+                    if (notedWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final NotedCallback cb = notedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op noted watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mNotedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = notedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+            for (int i = 0; i < mUidStates.size(); i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+                if (dumpWatchers || dumpHistory) {
+                    continue;
+                }
+                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+                    boolean hasOp = dumpOp < 0 || (opModes != null
+                            && opModes.indexOfKey(dumpOp) >= 0);
+                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+                    boolean hasMode = dumpMode < 0;
+                    if (!hasMode && opModes != null) {
+                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+                            if (opModes.valueAt(opi) == dumpMode) {
+                                hasMode = true;
+                            }
+                        }
+                    }
+                    if (pkgOps != null) {
+                        for (int pkgi = 0;
+                                (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+                                pkgi++) {
+                            Ops ops = pkgOps.valueAt(pkgi);
+                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+                                hasOp = true;
+                            }
+                            if (!hasMode) {
+                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+                                    if (ops.valueAt(opi).getMode() == dumpMode) {
+                                        hasMode = true;
+                                    }
+                                }
+                            }
+                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+                                hasPackage = true;
+                            }
+                        }
+                    }
+                    if (uidState.foregroundOps != null && !hasOp) {
+                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+                            hasOp = true;
+                        }
+                    }
+                    if (!hasOp || !hasPackage || !hasMode) {
+                        continue;
+                    }
+                }
+
+                pw.print("  Uid ");
+                UserHandle.formatUid(pw, uidState.uid);
+                pw.println(":");
+                uidState.dump(pw, nowElapsed);
+                if (uidState.foregroundOps != null && (dumpMode < 0
+                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+                    pw.println("    foregroundOps:");
+                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+                        pw.print(": ");
+                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+                    }
+                    pw.print("    hasForegroundWatchers=");
+                    pw.println(uidState.hasForegroundWatchers);
+                }
+                needSep = true;
+
+                if (opModes != null) {
+                    final int opModeCount = opModes.size();
+                    for (int j = 0; j < opModeCount; j++) {
+                        final int code = opModes.keyAt(j);
+                        final int mode = opModes.valueAt(j);
+                        if (dumpOp >= 0 && dumpOp != code) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != mode) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(code));
+                        pw.print(": mode=");
+                        pw.println(AppOpsManager.modeToName(mode));
+                    }
+                }
+
+                if (pkgOps == null) {
+                    continue;
+                }
+
+                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+                    final Ops ops = pkgOps.valueAt(pkgi);
+                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+                        continue;
+                    }
+                    boolean printedPackage = false;
+                    for (int j = 0; j < ops.size(); j++) {
+                        final Op op = ops.valueAt(j);
+                        final int opCode = op.op;
+                        if (dumpOp >= 0 && dumpOp != opCode) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
+                            continue;
+                        }
+                        if (!printedPackage) {
+                            pw.print("    Package ");
+                            pw.print(ops.packageName);
+                            pw.println(":");
+                            printedPackage = true;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(opCode));
+                        pw.print(" (");
+                        pw.print(AppOpsManager.modeToName(op.getMode()));
+                        final int switchOp = AppOpsManager.opToSwitch(opCode);
+                        if (switchOp != opCode) {
+                            pw.print(" / switch ");
+                            pw.print(AppOpsManager.opToName(switchOp));
+                            final Op switchObj = ops.get(switchOp);
+                            int mode = switchObj == null
+                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+                            pw.print("=");
+                            pw.print(AppOpsManager.modeToName(mode));
+                        }
+                        pw.println("): ");
+                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+                                sdf, date, "        ");
+                    }
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+
+            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
+        }
+
+        // Must not hold the appops lock
+        if (dumpHistory && !dumpWatchers) {
+            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+                    dumpFilter);
+        }
+        if (includeDiscreteOps) {
+            pw.println("Discrete accesses: ");
+            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
+        }
+    }
+
+    @Override
+    public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
+        checkSystemUid("setUserRestrictions");
+        Objects.requireNonNull(restrictions);
+        Objects.requireNonNull(token);
+        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+            String restriction = AppOpsManager.opToRestriction(i);
+            if (restriction != null) {
+                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+                        userHandle, null);
+            }
+        }
+    }
+
+    @Override
+    public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            PackageTagsList excludedPackageTags) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), null);
+        }
+        if (userHandle != UserHandle.getCallingUserId()) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+                    && mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+                        + " INTERACT_ACROSS_USERS to interact cross user ");
+            }
+        }
+        verifyIncomingOp(code);
+        Objects.requireNonNull(token);
+        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+    }
+
+    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+            int userHandle, PackageTagsList excludedPackageTags) {
+        synchronized (AppOpsServiceImpl.this) {
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientUserRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpUserRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+                    userHandle)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+                        restricted, userHandle));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpUserRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    @Override
+    public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            throw new SecurityException("Only the system can set global restrictions");
+        }
+
+        synchronized (this) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientGlobalRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpGlobalRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+                        restricted, UserHandle.USER_ALL));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpGlobalRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    @Override
+    public int getOpRestrictionCount(int code, UserHandle user, String pkg,
+            String attributionTag) {
+        int number = 0;
+        synchronized (this) {
+            int numRestrictions = mOpUserRestrictions.size();
+            for (int i = 0; i < numRestrictions; i++) {
+                if (mOpUserRestrictions.valueAt(i)
+                        .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+                                false)) {
+                    number++;
+                }
+            }
+
+            numRestrictions = mOpGlobalRestrictions.size();
+            for (int i = 0; i < numRestrictions; i++) {
+                if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+                    number++;
+                }
+            }
+        }
+
+        return number;
+    }
+
+    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+        synchronized (AppOpsServiceImpl.this) {
+            int numUids = mUidStates.size();
+            for (int uidNum = 0; uidNum < numUids; uidNum++) {
+                int uid = mUidStates.keyAt(uidNum);
+                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+                    continue;
+                }
+                updateStartedOpModeForUidLocked(code, restricted, uid);
+            }
+        }
+    }
+
+    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null || uidState.pkgOps == null) {
+            return;
+        }
+
+        int numPkgOps = uidState.pkgOps.size();
+        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+            Ops ops = uidState.pkgOps.valueAt(pkgNum);
+            Op op = ops != null ? ops.get(code) : null;
+            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+                continue;
+            }
+            int numAttrTags = op.mAttributions.size();
+            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+                if (restricted && attrOp.isRunning()) {
+                    attrOp.pause();
+                } else if (attrOp.isPaused()) {
+                    attrOp.resume();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void notifyWatchersOfChange(int code, int uid) {
+        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+        synchronized (this) {
+            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+            if (modeChangedListenerSet == null) {
+                return;
+            }
+        }
+
+        notifyOpChanged(modeChangedListenerSet, code, uid, null);
+    }
+
+    @Override
+    public void removeUser(int userHandle) throws RemoteException {
+        checkSystemUid("removeUser");
+        synchronized (AppOpsServiceImpl.this) {
+            final int tokenCount = mOpUserRestrictions.size();
+            for (int i = tokenCount - 1; i >= 0; i--) {
+                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+                opRestrictions.removeUser(userHandle);
+            }
+            removeUidsForUserLocked(userHandle);
+        }
+    }
+
+    @Override
+    public boolean isOperationActive(int code, int uid, String packageName) {
+        if (Binder.getCallingUid() != uid) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return false;
+        }
+
+        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return false;
+        }
+        // TODO moltmann: Allow to check for attribution op activeness
+        synchronized (AppOpsServiceImpl.this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+            if (pkgOps == null) {
+                return false;
+            }
+
+            Op op = pkgOps.get(code);
+            if (op == null) {
+                return false;
+            }
+
+            return op.isRunning();
+        }
+    }
+
+    @Override
+    public boolean isProxying(int op, @NonNull String proxyPackageName,
+            @NonNull String proxyAttributionTag, int proxiedUid,
+            @NonNull String proxiedPackageName) {
+        Objects.requireNonNull(proxyPackageName);
+        Objects.requireNonNull(proxiedPackageName);
+        final long callingUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+                    proxiedPackageName, new int[]{op});
+            if (packageOps == null || packageOps.isEmpty()) {
+                return false;
+            }
+            final List<OpEntry> opEntries = packageOps.get(0).getOps();
+            if (opEntries.isEmpty()) {
+                return false;
+            }
+            final OpEntry opEntry = opEntries.get(0);
+            if (!opEntry.isRunning()) {
+                return false;
+            }
+            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+            return proxyInfo != null && callingUid == proxyInfo.getUid()
+                    && proxyPackageName.equals(proxyInfo.getPackageName())
+                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void resetPackageOpsNoHistory(@NonNull String packageName) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetPackageOpsNoHistory");
+        synchronized (AppOpsServiceImpl.this) {
+            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+                    UserHandle.getCallingUserId());
+            if (uid == Process.INVALID_UID) {
+                return;
+            }
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null || uidState.pkgOps == null) {
+                return;
+            }
+            Ops removedOps = uidState.pkgOps.remove(packageName);
+            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    @Override
+    public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "setHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+    }
+
+    @Override
+    public void offsetHistory(long offsetMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "offsetHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.offsetHistory(offsetMillis);
+        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+    }
+
+    @Override
+    public void addHistoricalOps(HistoricalOps ops) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "addHistoricalOps");
+        // Must not hold the appops lock
+        mHistoricalRegistry.addHistoricalOps(ops);
+    }
+
+    @Override
+    public void resetHistoryParameters() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.resetHistoryParameters();
+    }
+
+    @Override
+    public void clearHistory() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "clearHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.clearAllHistory();
+    }
+
+    @Override
+    public void rebootHistory(long offlineDurationMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "rebootHistory");
+
+        Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+        // Must not hold the appops lock
+        mHistoricalRegistry.shutdown();
+
+        if (offlineDurationMillis > 0) {
+            SystemClock.sleep(offlineDurationMillis);
+        }
+
+        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+        mHistoricalRegistry.persistPendingHistory();
+    }
+
+    @GuardedBy("this")
+    private void removeUidsForUserLocked(int userHandle) {
+        for (int i = mUidStates.size() - 1; i >= 0; --i) {
+            final int uid = mUidStates.keyAt(i);
+            if (UserHandle.getUserId(uid) == userHandle) {
+                mUidStates.valueAt(i).clear();
+                mUidStates.removeAt(i);
+            }
+        }
+    }
+
+    private void checkSystemUid(String function) {
+        int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException(function + " must by called by the system");
+        }
+    }
+
+    private static int resolveUid(String packageName) {
+        if (packageName == null) {
+            return Process.INVALID_UID;
+        }
+        switch (packageName) {
+            case "root":
+                return Process.ROOT_UID;
+            case "shell":
+            case "dumpstate":
+                return Process.SHELL_UID;
+            case "media":
+                return Process.MEDIA_UID;
+            case "audioserver":
+                return Process.AUDIOSERVER_UID;
+            case "cameraserver":
+                return Process.CAMERASERVER_UID;
+        }
+        return Process.INVALID_UID;
+    }
+
+    private static String[] getPackagesForUid(int uid) {
+        String[] packageNames = null;
+
+        // Very early during boot the package manager is not yet or not yet fully started. At this
+        // time there are no packages yet.
+        if (AppGlobals.getPackageManager() != null) {
+            try {
+                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+        if (packageNames == null) {
+            return EmptyArray.STRING;
+        }
+        return packageNames;
+    }
+
+    private final class ClientUserRestrictionState implements DeathRecipient {
+        private final IBinder mToken;
+
+        ClientUserRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        public boolean setRestriction(int code, boolean restricted,
+                PackageTagsList excludedPackageTags, int userId) {
+            return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
+                    restricted, excludedPackageTags);
+        }
+
+        public boolean hasRestriction(int code, String packageName, String attributionTag,
+                int userId, boolean isCheckOp) {
+            return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
+                    attributionTag, isCheckOp);
+        }
+
+        public void removeUser(int userId) {
+            mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
+        }
+
+        public boolean isDefault() {
+            return !mAppOpsRestrictions.hasUserRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (AppOpsServiceImpl.this) {
+                mAppOpsRestrictions.clearUserRestrictions(mToken);
+                mOpUserRestrictions.remove(mToken);
+                destroy();
+            }
+        }
+
+        public void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
+    private final class ClientGlobalRestrictionState implements DeathRecipient {
+        final IBinder mToken;
+
+        ClientGlobalRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        boolean setRestriction(int code, boolean restricted) {
+            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+        }
+
+        boolean hasRestriction(int code) {
+            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+        }
+
+        boolean isDefault() {
+            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+            mOpGlobalRestrictions.remove(mToken);
+            destroy();
+        }
+
+        void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
+    @Override
+    public void setDeviceAndProfileOwners(SparseIntArray owners) {
+        synchronized (this) {
+            mProfileOwners = owners;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index 18f659e..8420fcb 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -13,197 +13,482 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.server.appop;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager.Mode;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.AttributionSource;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PackageTagsList;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
- * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * This interface also includes functions for added and removing op mode watchers.
- * In the future this interface will also include op restrictions.
+ *
  */
-public interface AppOpsServiceInterface {
-    /**
-     * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
-     * Returns an empty SparseIntArray if nothing is set.
-     * @param uid for which we need the app-ops and their modes.
-     */
-    SparseIntArray getNonDefaultUidModes(int uid);
+public interface AppOpsServiceInterface extends PersistenceScheduler {
 
     /**
-     * Returns the app-op mode for a particular app-op of a uid.
-     * Returns default op mode if the op mode for particular uid and op is not set.
-     * @param uid user id for which we need the mode.
-     * @param op app-op for which we need the mode.
-     * @return mode of the app-op.
-     */
-    int getUidMode(int uid, int op);
-
-    /**
-     * Set the app-op mode for a particular uid and op.
-     * The mode is not set if the mode is the same as the default mode for the op.
-     * @param uid user id for which we want to set the mode.
-     * @param op app-op for which we want to set the mode.
-     * @param mode mode for the app-op.
-     * @return true if op mode is changed.
-     */
-    boolean setUidMode(int uid, int op, @Mode int mode);
-
-    /**
-     * Gets the app-op mode for a particular package.
-     * Returns default op mode if the op mode for the particular package is not set.
-     * @param packageName package name for which we need the op mode.
-     * @param op app-op for which we need the mode.
-     * @param userId user id associated with the package.
-     * @return the mode of the app-op.
-     */
-    int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
-
-    /**
-     * Sets the app-op mode for a particular package.
-     * @param packageName package name for which we need to set the op mode.
-     * @param op app-op for which we need to set the mode.
-     * @param mode the mode of the app-op.
-     * @param userId user id associated with the package.
      *
      */
-    void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+    void systemReady();
 
     /**
-     * Stop tracking any app-op modes for a package.
-     * @param packageName Name of the package for which we want to remove all mode tracking.
-     * @param userId user id associated with the package.
+     *
      */
-    boolean removePackage(@NonNull String packageName,  @UserIdInt int userId);
+    void shutdown();
 
     /**
-     * Stop tracking any app-op modes for this uid.
-     * @param uid user id for which we want to remove all tracking.
+     *
+     * @param uid
+     * @param packageName
      */
-    void removeUid(int uid);
+    void verifyPackage(int uid, String packageName);
 
     /**
-     * Returns true if all uid modes for this uid are
-     * in default state.
-     * @param uid user id
+     *
+     * @param op
+     * @param packageName
+     * @param flags
+     * @param callback
      */
-    boolean areUidModesDefault(int uid);
+    void startWatchingModeWithFlags(int op, String packageName, int flags,
+            IAppOpsCallback callback);
 
     /**
-     * Returns true if all package modes for this package name are
-     * in default state.
-     * @param packageName package name.
-     * @param userId user id associated with the package.
+     *
+     * @param callback
      */
-    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+    void stopWatchingMode(IAppOpsCallback callback);
 
     /**
-     * Stop tracking app-op modes for all uid and packages.
+     *
+     * @param ops
+     * @param callback
      */
-    void clearAllModes();
+    void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
 
     /**
-     * Registers changedListener to listen to op's mode change.
-     * @param changedListener the listener that must be trigger on the op's mode change.
-     * @param op op representing the app-op whose mode change needs to be listened to.
+     *
+     * @param callback
      */
-    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+    void stopWatchingActive(IAppOpsActiveCallback callback);
 
     /**
-     * Registers changedListener to listen to package's app-op's mode change.
-     * @param changedListener the listener that must be trigger on the mode change.
-     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     *
+     * @param ops
+     * @param callback
      */
-    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName);
+    void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
 
     /**
-     * Stop the changedListener from triggering on any mode change.
-     * @param changedListener the listener that needs to be removed.
+     *
+     * @param callback
      */
-    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+    void stopWatchingStarted(IAppOpsStartedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
-     * @param op app-op whose mode change is being listened to.
+     *
+     * @param ops
+     * @param callback
      */
-    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+    void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
-     * @param packageName of package whose app-op's mode change is being listened to.
+     *
+     * @param callback
      */
-    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+    void stopWatchingNoted(IAppOpsNotedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param startIfModeDefault
+     * @param message
+     * @param attributionFlags
+     * @param attributionChainId
+     * @return
      */
-    void notifyWatchersOfChange(int op, int uid);
+    int startOperation(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, @NonNull String message,
+            @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId);
+
+
+    int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
+            boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param changedListener the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param packageName package name that is associated with the app-op
+     *
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
      */
-    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName);
+    void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed to all packages associated with the uid by
-     * triggering the appropriate change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param onlyForeground true if only watchers that
-     * @param callbackToIgnore callback that should be ignored.
+     *
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
      */
-    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore);
+    void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag);
 
     /**
-     * TODO: Move hasForegroundWatchers and foregroundOps into this.
-     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param uid for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @return  foregroundOps.
+     *
+     * @param uidPackageNames
+     * @param visible
      */
-    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+    void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
 
     /**
-     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param packageName for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @param userId user id associated with the package.
-     * @return foregroundOps.
+     *
      */
-    SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+    void readState();
 
     /**
-     * Dump op mode and package mode listeners and their details.
-     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
-     *               app-op, only the watchers for that app-op are dumped.
-     * @param dumpUid uid for which we want to dump op mode watchers.
-     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
-     * @param printWriter writer to dump to.
+     *
      */
-    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+    void writeState();
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     */
+    void packageRemoved(int uid, String packageName);
+
+    /**
+     *
+     * @param uid
+     */
+    void uidRemoved(int uid);
+
+    /**
+     *
+     * @param uid
+     * @param procState
+     * @param capability
+     */
+    void updateUidProcState(int uid, int procState,
+            @ActivityManager.ProcessCapability int capability);
+
+    /**
+     *
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param opNames
+     * @param dataType
+     * @param filter
+     * @param beginTimeMillis
+     * @param endTimeMillis
+     * @param flags
+     * @param callback
+     */
+    void getHistoricalOps(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param opNames
+     * @param dataType
+     * @param filter
+     * @param beginTimeMillis
+     * @param endTimeMillis
+     * @param flags
+     * @param callback
+     */
+    void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback);
+
+    /**
+     *
+     */
+    void reloadNonHistoricalState();
+
+    /**
+     *
+     * @param uid
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
+
+    /**
+     *
+     * @param owners
+     */
+    void setDeviceAndProfileOwners(SparseIntArray owners);
+
+    // used in audio restriction calls, might just copy the logic to avoid having this call.
+    /**
+     *
+     * @param callingPid
+     * @param callingUid
+     * @param targetUid
+     */
+    void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param mode
+     * @param permissionPolicyCallback
+     */
+    void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param mode
+     * @param permissionPolicyCallback
+     */
+    void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback);
+
+    /**
+     *
+     * @param reqUserId
+     * @param reqPackageName
+     */
+    void resetAllModes(int reqUserId, String reqPackageName);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param raw
+     * @return
+     */
+    int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @return
+     */
+    int checkPackage(int uid, String packageName);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param message
+     * @return
+     */
+    int noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String message);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param proxyUid
+     * @param proxyPackageName
+     * @param proxyAttributionTag
+     * @param flags
+     * @return
+     */
+    @AppOpsManager.Mode
+    int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
+
+    boolean isAttributionTagValid(int uid, @NonNull String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName);
+
+    /**
+     *
+     * @param fd
+     * @param pw
+     * @param args
+     */
+    @NeverCompile
+        // Avoid size overhead of debugging code.
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    /**
+     *
+     * @param restrictions
+     * @param token
+     * @param userHandle
+     */
+    void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
+
+    /**
+     *
+     * @param code
+     * @param restricted
+     * @param token
+     * @param userHandle
+     * @param excludedPackageTags
+     */
+    void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            PackageTagsList excludedPackageTags);
+
+    /**
+     *
+     * @param code
+     * @param restricted
+     * @param token
+     */
+    void setGlobalRestriction(int code, boolean restricted, IBinder token);
+
+    /**
+     *
+     * @param code
+     * @param user
+     * @param pkg
+     * @param attributionTag
+     * @return
+     */
+    int getOpRestrictionCount(int code, UserHandle user, String pkg,
+            String attributionTag);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     */
+    // added to interface for audio restriction stuff
+    void notifyWatchersOfChange(int code, int uid);
+
+    /**
+     *
+     * @param userHandle
+     * @throws RemoteException
+     */
+    void removeUser(int userHandle) throws RemoteException;
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @return
+     */
+    boolean isOperationActive(int code, int uid, String packageName);
+
+    /**
+     *
+     * @param op
+     * @param proxyPackageName
+     * @param proxyAttributionTag
+     * @param proxiedUid
+     * @param proxiedPackageName
+     * @return
+     */
+    // TODO this one might not need to be in the interface
+    boolean isProxying(int op, @NonNull String proxyPackageName,
+            @NonNull String proxyAttributionTag, int proxiedUid,
+            @NonNull String proxiedPackageName);
+
+    /**
+     *
+     * @param packageName
+     */
+    void resetPackageOpsNoHistory(@NonNull String packageName);
+
+    /**
+     *
+     * @param mode
+     * @param baseSnapshotInterval
+     * @param compressionStep
+     */
+    void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep);
+
+    /**
+     *
+     * @param offsetMillis
+     */
+    void offsetHistory(long offsetMillis);
+
+    /**
+     *
+     * @param ops
+     */
+    void addHistoricalOps(AppOpsManager.HistoricalOps ops);
+
+    /**
+     *
+     */
+    void resetHistoryParameters();
+
+    /**
+     *
+     */
+    void clearHistory();
+
+    /**
+     *
+     * @param offlineDurationMillis
+     */
+    void rebootHistory(long offlineDurationMillis);
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 5114bd5..c1434e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -59,7 +59,7 @@
     private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
-    private AppOpsService.Constants mConstants;
+    private AppOpsServiceImpl.Constants mConstants;
 
     private SparseIntArray mUidStates = new SparseIntArray();
     private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -85,7 +85,7 @@
 
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
             Handler handler, Executor lockingExecutor, Clock clock,
-            AppOpsService.Constants constants) {
+            AppOpsServiceImpl.Constants constants) {
 
         this(activityManagerInternal, new DelayableExecutor() {
             @Override
@@ -102,7 +102,7 @@
 
     @VisibleForTesting
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+            DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
             Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
         mExecutor = executor;
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index dcc36bc..7970269 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@
 import java.util.NoSuchElementException;
 
 final class AttributedOp {
-    private final @NonNull AppOpsService mAppOpsService;
+    private final @NonNull AppOpsServiceImpl mAppOpsService;
     public final @Nullable String tag;
-    public final @NonNull AppOpsService.Op parent;
+    public final @NonNull AppOpsServiceImpl.Op parent;
 
     /**
      * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@
     // @GuardedBy("mAppOpsService")
     @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
 
-    AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
-            @NonNull AppOpsService.Op parent) {
+    AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
+                 @NonNull AppOpsServiceImpl.Op parent) {
         mAppOpsService = appOpsService;
         this.tag = tag;
         this.parent = parent;
@@ -131,8 +131,8 @@
 
         AppOpsManager.OpEventProxyInfo proxyInfo = null;
         if (proxyUid != Process.INVALID_UID) {
-            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                    proxyAttributionTag);
+            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
+                    proxyPackageName, proxyAttributionTag);
         }
 
         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@
         if (event == null) {
             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
                     SystemClock.elapsedRealtime(), clientId, tag,
-                    PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
+                    PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
                     proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
                     attributionFlags, attributionChainId);
             events.put(clientId, event);
@@ -251,9 +251,9 @@
         event.mNumUnfinishedStarts++;
 
         if (isStarted) {
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                    parent.packageName, tag, uidState, flags, startTime, attributionFlags,
-                    attributionChainId);
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+                    parent.uid, parent.packageName, tag, uidState, flags, startTime,
+                    attributionFlags, attributionChainId);
         }
     }
 
@@ -309,8 +309,8 @@
             mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
                     finishedEvent);
 
-            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
-                    parent.packageName, tag, event.getUidState(),
+            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
+                    parent.uid, parent.packageName, tag, event.getUidState(),
                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                     event.getAttributionFlags(), event.getAttributionChainId());
 
@@ -334,13 +334,13 @@
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
         if (!isPaused()) {
-            Slog.wtf(AppOpsService.TAG, "No ops running or paused");
+            Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
             return;
         }
 
         int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
         if (indexOfToken < 0) {
-            Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
+            Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
             return;
         } else if (isPausing) {
             // already paused
@@ -416,9 +416,9 @@
             mInProgressEvents.put(event.getClientId(), event);
             event.setStartElapsedTime(SystemClock.elapsedRealtime());
             event.setStartTime(startTime);
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                    parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
-                    event.getAttributionFlags(), event.getAttributionChainId());
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+                    parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
+                    startTime, event.getAttributionFlags(), event.getAttributionChainId());
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@
                         newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
                     }
                 } catch (RemoteException e) {
-                    if (AppOpsService.DEBUG) {
-                        Slog.e(AppOpsService.TAG,
+                    if (AppOpsServiceImpl.DEBUG) {
+                        Slog.e(AppOpsServiceImpl.TAG,
                                 "Cannot switch to new uidState " + newState);
                     }
                 }
@@ -555,8 +555,8 @@
             ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
                     opToAdd.isRunning()
                             ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
-            Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
-                    + opToAdd.isRunning());
+            Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
+                    + " app-ops, running: " + opToAdd.isRunning());
 
             int numInProgressEvents = ignoredEvents.size();
             for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,16 +668,22 @@
         /**
          * Create a new {@link InProgressStartOpEvent}.
          *
-         * @param startTime          The time {@link #startOperation} was called
-         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
-         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
+         *                          was called
+         * @param startElapsedTime   The elapsed time whe
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         * @param clientId           The client id of the caller of
+         *                          {@link AppOpCheckingServiceInterface#startOperation}
          * @param attributionTag     The attribution tag for the operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param uidState           The uidstate of the app
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         *                          for
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
-         * @param proxy              The proxy information, if {@link #startProxyOperation} was
-         *                           called
+         * @param proxy              The proxy information, if
+         *                          {@link AppOpCheckingServiceInterface#startProxyOperation} was
+         *                          called
          * @param flags              The trusted/nontrusted/self flags.
          * @throws RemoteException If the client is dying
          */
@@ -718,15 +724,21 @@
         /**
          * Reinit existing object with new state.
          *
-         * @param startTime          The time {@link #startOperation} was called
-         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
-         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
+         *                          was called
+         * @param startElapsedTime   The elapsed time when
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         * @param clientId           The client id of the caller of
+         *                          {@link AppOpCheckingServiceInterface#startOperation}
          * @param attributionTag     The attribution tag for this operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param uidState           The uidstate of the app
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         *                          for
          * @param flags              The flags relating to the proxy
-         * @param proxy              The proxy information, if {@link #startProxyOperation}
-         *                           was called
+         * @param proxy              The proxy information, if
+         *                          {@link AppOpCheckingServiceInterface#startProxyOperation was
+         *                          called
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param proxyPool          The pool to release
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3231240..8aa898e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5863,6 +5863,9 @@
     };
 
     private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        if (!device.isSink()) {
+            return false;
+        }
         for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
             if (device.getType() == type) {
                 return true;
@@ -5897,7 +5900,11 @@
                 throw new IllegalArgumentException("invalid portID " + portId);
             }
             if (!isValidCommunicationDevice(device)) {
-                throw new IllegalArgumentException("invalid device type " + device.getType());
+                if (!device.isSink()) {
+                    throw new IllegalArgumentException("device must have sink role");
+                } else {
+                    throw new IllegalArgumentException("invalid device type: " + device.getType());
+                }
             }
         }
         final String eventSource = new StringBuilder()
@@ -7092,9 +7099,10 @@
 
     private @AudioManager.DeviceVolumeBehavior
             int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
-        // translate Java device type to native device type (for the devices masks for full / fixed)
-        final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
-                device.getType());
+        // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+        // exact (avoids double conversions) than a conversion from SDK type via
+        // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+        final int audioSystemDeviceOut = device.getInternalType();
 
         int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
         if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
@@ -7180,6 +7188,24 @@
                         state == CONNECTION_STATE_CONNECTED ? "connected" : "disconnected")
                 .record();
         mDeviceBroker.setWiredDeviceConnectionState(attributes, state, caller);
+        // The Dynamic Soundbar mode feature introduces dynamic presence for an HDMI Audio System
+        // Client. For example, the device can start with the Audio System Client unavailable.
+        // When the feature is activated the client becomes available, therefore Audio Service
+        // requests a new HDMI Audio System Client instance when the ARC status is changed.
+        if (attributes.getInternalType() == AudioSystem.DEVICE_IN_HDMI_ARC) {
+            updateHdmiAudioSystemClient();
+        }
+    }
+
+    /**
+     * Replace the current HDMI Audio System Client.
+     * See {@link #setWiredDeviceConnectionState(AudioDeviceAttributes, int, String)}.
+     */
+    private void updateHdmiAudioSystemClient() {
+        Slog.d(TAG, "Hdmi Audio System Client is updated");
+        synchronized (mHdmiClientLock) {
+            mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
+        }
     }
 
     /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index d39d2d1..1b20e43 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -18,9 +18,9 @@
 
 import android.app.IWallpaperManager;
 import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupHelper;
-import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.WallpaperBackupHelper;
@@ -89,8 +89,8 @@
     private int mUserId = UserHandle.USER_SYSTEM;
 
     @Override
-    public void onCreate(UserHandle user, @BackupManager.OperationType int operationType) {
-        super.onCreate(user, operationType);
+    public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
+        super.onCreate(user, backupDestination);
 
         mUserId = user.getIdentifier();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 1c57151..229393d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -421,13 +421,6 @@
                 return -1;
             }
 
-            if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
-                // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
-                // ever be invoked when the user is encrypted or lockdown.
-                Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
-                return -1;
-            }
-
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index b882c47..e16ca0b 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -551,6 +551,15 @@
                     lensFacing, ignoreResizableAndSdkCheck);
         }
 
+        /**
+         * Placeholder method to fetch the system state for autoframing.
+         * TODO: b/260617354
+         */
+        @Override
+        public int getAutoframingOverride(String packageName) {
+            return CaptureRequest.CONTROL_AUTOFRAMING_OFF;
+        }
+
         @Override
         public void pingForUserUpdate() {
             if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 84dfe86..a0cbd7f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -158,6 +158,19 @@
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16;
 
     /**
+     * Flag: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public static final int FLAG_OWN_FOCUS = 1 << 17;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
@@ -584,9 +597,30 @@
         if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
             msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
         }
+        if ((flags & FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+            msg.append(", FLAG_DESTROY_CONTENT_ON_REMOVAL");
+        }
         if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) {
             msg.append(", FLAG_MASK_DISPLAY_CUTOUT");
         }
+        if ((flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+            msg.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS");
+        }
+        if ((flags & FLAG_TRUSTED) != 0) {
+            msg.append(", FLAG_TRUSTED");
+        }
+        if ((flags & FLAG_OWN_DISPLAY_GROUP) != 0) {
+            msg.append(", FLAG_OWN_DISPLAY_GROUP");
+        }
+        if ((flags & FLAG_ALWAYS_UNLOCKED) != 0) {
+            msg.append(", FLAG_ALWAYS_UNLOCKED");
+        }
+        if ((flags & FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+            msg.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
+        }
+        if ((flags & FLAG_OWN_FOCUS) != 0) {
+            msg.append(", FLAG_OWN_FOCUS");
+        }
         return msg.toString();
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 05cd67f..c5cb08d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -105,7 +105,6 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.sysprop.DisplayProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -451,8 +450,6 @@
         }
     };
 
-    private final boolean mAllowNonNativeRefreshRateOverride;
-
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
     /**
@@ -506,7 +503,6 @@
         ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
-        mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
         mSystemReady = false;
     }
 
@@ -930,24 +926,20 @@
             }
         }
 
-        if (mAllowNonNativeRefreshRateOverride) {
-            overriddenInfo.refreshRateOverride = frameRateHz;
-            if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                    callingUid)) {
-                overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
-                        info.supportedModes.length + 1);
-                overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
-                        new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
-                                currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
-                                overriddenInfo.refreshRateOverride);
-                overriddenInfo.modeId =
-                        overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
-                                .getModeId();
-            }
-            return overriddenInfo;
+        overriddenInfo.refreshRateOverride = frameRateHz;
+        if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+                callingUid)) {
+            overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+                    info.supportedModes.length + 1);
+            overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+                    new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+                            currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+                            overriddenInfo.refreshRateOverride);
+            overriddenInfo.modeId =
+                    overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+                            .getModeId();
         }
-
-        return info;
+        return overriddenInfo;
     }
 
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -2602,11 +2594,6 @@
         long getDefaultDisplayDelayTimeout() {
             return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
         }
-
-        boolean getAllowNonNativeRefreshRateOverride() {
-            return DisplayProperties
-                    .debug_allow_non_native_refresh_rate_override().orElse(true);
-        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 9ded42a..1f58a1c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -299,7 +299,6 @@
     private boolean mAppliedAutoBrightness;
     private boolean mAppliedDimming;
     private boolean mAppliedLowPower;
-    private boolean mAppliedTemporaryBrightness;
     private boolean mAppliedTemporaryAutoBrightnessAdjustment;
     private boolean mAppliedBrightnessBoost;
     private boolean mAppliedThrottling;
@@ -395,11 +394,6 @@
     // behalf of the user.
     private float mCurrentScreenBrightnessSetting;
 
-    // The temporary screen brightness. Typically set when a user is interacting with the
-    // brightness slider but hasn't settled on a choice yet. Set to
-    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
-    private float mTemporaryScreenBrightness;
-
     // The current screen brightness while in VR mode.
     private float mScreenBrightnessForVr;
 
@@ -566,7 +560,6 @@
         mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
         mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
-        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -1218,16 +1211,6 @@
 
         final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
 
-        // Use the temporary screen brightness if there isn't an override, either from
-        // WindowManager or based on the display state.
-        if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
-            brightnessState = mTemporaryScreenBrightness;
-            mAppliedTemporaryBrightness = true;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
-        } else {
-            mAppliedTemporaryBrightness = false;
-        }
-
         final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
 
         // Use the autobrightness adjustment override if set.
@@ -1414,7 +1397,8 @@
         // Skip the animation when the screen is off or suspended or transition to/from VR.
         boolean brightnessAdjusted = false;
         final boolean brightnessIsTemporary =
-                mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+                (mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
+                        || mAppliedTemporaryAutoBrightnessAdjustment;
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
                 if (state == Display.STATE_ON) {
@@ -2202,13 +2186,15 @@
         }
         if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
             mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            mDisplayBrightnessController
+                    .setTemporaryBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
             return false;
         }
         setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
         mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
         mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mDisplayBrightnessController
+                .setTemporaryBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         return true;
     }
 
@@ -2291,7 +2277,6 @@
         pw.println("  mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
         pw.println("  mPendingScreenBrightnessSetting="
                 + mPendingScreenBrightnessSetting);
-        pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
         pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
@@ -2301,7 +2286,6 @@
         pw.println("  mAppliedDimming=" + mAppliedDimming);
         pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
-        pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
         pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
                 + mAppliedTemporaryAutoBrightnessAdjustment);
         pw.println("  mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
@@ -2541,7 +2525,8 @@
 
                 case MSG_SET_TEMPORARY_BRIGHTNESS:
                     // TODO: Should we have a a timeout for the temporary brightness?
-                    mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+                    mDisplayBrightnessController
+                            .setTemporaryBrightness(Float.intBitsToFloat(msg.arg1));
                     updatePowerState();
                     break;
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dedc56a..26ac528 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -384,6 +384,9 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
+            }
             Rect maskingInsets = getMaskingInsets(deviceInfo);
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 20b82c3..a23a073 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -21,6 +21,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
@@ -495,13 +496,25 @@
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
                     mInfo.flags |= FLAG_TRUSTED;
                 }
-                if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0
-                        && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
-                    mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
+                        mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+                    } else {
+                        Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it "
+                                + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+                    }
                 }
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                     mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED;
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+                        mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS;
+                    } else {
+                        Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires "
+                                + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+                    }
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
index d62b1ee..fd4e296 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -28,7 +28,7 @@
      * Checks whether the brightness is within the valid brightness range, not including off.
      */
     public static boolean isValidBrightnessValue(float brightness) {
-        return brightness >= PowerManager.BRIGHTNESS_MIN
+        return !Float.isNaN(brightness) && brightness >= PowerManager.BRIGHTNESS_MIN
                 && brightness <= PowerManager.BRIGHTNESS_MAX;
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 80b5e65..bdc8d9d 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -68,6 +68,21 @@
     }
 
     /**
+     * Sets the temporary brightness
+     */
+    public void setTemporaryBrightness(Float temporaryBrightness) {
+        mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
+                .setTemporaryScreenBrightness(temporaryBrightness);
+    }
+
+    /**
+     * Returns the current selected DisplayBrightnessStrategy
+     */
+    public DisplayBrightnessStrategy getCurrentDisplayBrightnessStrategy() {
+        return mDisplayBrightnessStrategy;
+    }
+
+    /**
      * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
      * brightness when dozing
      */
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index b83b13b..4759b7d 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.view.Display;
 
@@ -29,6 +30,7 @@
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
 import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
 
 import java.io.PrintWriter;
 
@@ -48,7 +50,9 @@
     // The brightness strategy used to manage the brightness state when the request state is
     // invalid.
     private final OverrideBrightnessStrategy mOverrideBrightnessStrategy;
-    // The brightness strategy used to manage the brightness state request is invalid.
+    // The brightness strategy used to manage the brightness state in temporary state
+    private final TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+    // The brightness strategy used to manage the brightness state when the request is invalid.
     private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
 
     // We take note of the old brightness strategy so that we can know when the strategy changes.
@@ -67,6 +71,7 @@
         mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
         mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
         mOverrideBrightnessStrategy = injector.getOverrideBrightnessStrategy();
+        mTemporaryBrightnessStrategy = injector.getTemporaryBrightnessStrategy();
         mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
@@ -89,6 +94,9 @@
         } else if (BrightnessUtils
                 .isValidBrightnessValue(displayPowerRequest.screenBrightnessOverride)) {
             displayBrightnessStrategy = mOverrideBrightnessStrategy;
+        } else if (BrightnessUtils.isValidBrightnessValue(
+                mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
+            displayBrightnessStrategy = mTemporaryBrightnessStrategy;
         }
 
         if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
@@ -101,6 +109,10 @@
         return displayBrightnessStrategy;
     }
 
+    public TemporaryBrightnessStrategy getTemporaryDisplayBrightnessStrategy() {
+        return mTemporaryBrightnessStrategy;
+    }
+
     /**
      * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
      * brightness when dozing
@@ -120,6 +132,8 @@
         writer.println(
                 "  mAllowAutoBrightnessWhileDozingConfig= "
                         + mAllowAutoBrightnessWhileDozingConfig);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+        mTemporaryBrightnessStrategy.dump(ipw);
     }
 
     /**
@@ -152,6 +166,10 @@
             return new OverrideBrightnessStrategy();
         }
 
+        TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+            return new TemporaryBrightnessStrategy();
+        }
+
         InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
             return new InvalidBrightnessStrategy();
         }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
new file mode 100644
index 0000000..f8063f3
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the brightness of the display when the system brightness is temporary
+ */
+public class TemporaryBrightnessStrategy implements DisplayBrightnessStrategy {
+    // The temporary screen brightness. Typically set when a user is interacting with the
+    // brightness slider but hasn't settled on a choice yet. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
+    private float mTemporaryScreenBrightness;
+
+    public TemporaryBrightnessStrategy() {
+        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    }
+
+    // Use the temporary screen brightness if there isn't an override, either from
+    // WindowManager or based on the display state.
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // Todo(brup): Introduce a validator class and add validations before setting the brightness
+        DisplayBrightnessState displayBrightnessState =
+                BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_TEMPORARY,
+                        mTemporaryScreenBrightness,
+                        mTemporaryScreenBrightness);
+        mTemporaryScreenBrightness = Float.NaN;
+        return displayBrightnessState;
+    }
+
+    @Override
+    public String getName() {
+        return "TemporaryBrightnessStrategy";
+    }
+
+    public float getTemporaryScreenBrightness() {
+        return mTemporaryScreenBrightness;
+    }
+
+    public void setTemporaryScreenBrightness(float temporaryScreenBrightness) {
+        mTemporaryScreenBrightness = temporaryScreenBrightness;
+    }
+
+    /**
+     * Dumps the state of this class.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("TemporaryBrightnessStrategy:");
+        writer.println("  mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 049a339..4855be6 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -27,7 +27,7 @@
     private static final int STATE_ARC_TERMINATED = 2;
 
     // the required maximum response time specified in CEC 9.2
-    private static final int TIMEOUT_MS = 1000;
+    public static final int TIMEOUT_MS = 1000;
 
     ArcTerminationActionFromAvr(HdmiCecLocalDevice source) {
         super(source);
@@ -85,6 +85,8 @@
     }
 
     private void handleTerminateArcTimeout() {
+        // Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
+        audioSystem().setArcStatus(false);
         HdmiLogger.debug("handleTerminateArcTimeout");
         finish();
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 827aafa..6925507 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -318,6 +318,16 @@
                 R.bool.config_cecRoutingControlDisabled_allowed,
                 R.bool.config_cecRoutingControlDisabled_default);
 
+        Setting soundbarMode = registerSetting(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                R.bool.config_cecSoundbarMode_userConfigurable);
+        soundbarMode.registerValue(HdmiControlManager.SOUNDBAR_MODE_ENABLED,
+                R.bool.config_cecSoundbarModeEnabled_allowed,
+                R.bool.config_cecSoundbarModeEnabled_default);
+        soundbarMode.registerValue(HdmiControlManager.SOUNDBAR_MODE_DISABLED,
+                R.bool.config_cecSoundbarModeDisabled_allowed,
+                R.bool.config_cecSoundbarModeDisabled_default);
+
         Setting powerControlMode = registerSetting(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                 R.bool.config_cecPowerControlMode_userConfigurable);
@@ -714,6 +724,8 @@
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
                 return STORAGE_SHARED_PREFS;
+            case HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE:
+                return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
@@ -789,6 +801,8 @@
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
                 return setting.getName();
+            case HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE:
+                return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 5c1b33c..50edd0e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -636,7 +636,7 @@
     void onReceiveCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_IGNORE) == 0)
-                && !mService.isControlEnabled()
+                && !mService.isCecControlEnabled()
                 && !HdmiCecMessage.isCecTransportMessage(message.getOpcode())) {
             if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_LOG_WARNING) != 0) {
                 HdmiLogger.warning("Message " + message + " received when cec disabled");
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 2622cef..b4d7fb9 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -698,7 +698,7 @@
 
     protected void reportFeatures() {
         List<Integer> localDeviceTypes = new ArrayList<>();
-        for (HdmiCecLocalDevice localDevice : mService.getAllLocalDevices()) {
+        for (HdmiCecLocalDevice localDevice : mService.getAllCecLocalDevices()) {
             localDeviceTypes.add(localDevice.mDeviceType);
         }
 
@@ -728,7 +728,7 @@
     protected int handleStandby(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #12
-        if (mService.isControlEnabled()
+        if (mService.isCecControlEnabled()
                 && !mService.isProhibitMode()
                 && mService.isPowerOnOrTransient()) {
             mService.standby();
@@ -1359,7 +1359,8 @@
         List<SendKeyAction> action = getActions(SendKeyAction.class);
         int logicalAddress = findAudioReceiverAddress();
         if (logicalAddress == Constants.ADDR_INVALID
-                || logicalAddress == mDeviceInfo.getLogicalAddress()) {
+                || mService.getAllCecLocalDevices().stream().anyMatch(
+                        device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) {
             // Don't send key event to invalid device or itself.
             Slog.w(
                     TAG,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 32ff5e22..ccaa9255d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -226,6 +226,8 @@
     @Override
     @ServiceThreadOnly
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+        terminateAudioReturnChannel();
+
         super.disableDevice(initiatedByCec, callback);
         assertRunOnServiceThread();
         mService.unregisterTvInputCallback(mTvInputCallback);
@@ -884,7 +886,7 @@
     private void notifyArcStatusToAudioService(boolean enabled) {
         // Note that we don't set any name to ARC.
         mService.getAudioManager()
-            .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+            .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI_ARC, enabled ? 1 : 0, "", "");
     }
 
     void reportAudioStatus(int source) {
@@ -1042,7 +1044,7 @@
             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
             return;
         }
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setRoutingPort(portId);
             setLocalActivePort(portId);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
@@ -1088,6 +1090,16 @@
         }
     }
 
+    private void terminateAudioReturnChannel() {
+        // remove pending initiation actions
+        removeAction(ArcInitiationActionFromAvr.class);
+        if (!isArcEnabled()
+                || !mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
+            return;
+        }
+        addAndStartAction(new ArcTerminationActionFromAvr(this));
+    }
+
     /** Reports if System Audio Mode is supported by the connected TV */
     interface TvSystemAudioModeSupportedCallback {
 
@@ -1312,6 +1324,9 @@
     @ServiceThreadOnly
     private void launchDeviceDiscovery() {
         assertRunOnServiceThread();
+        if (mService.isDeviceDiscoveryHandledByPlayback()) {
+            return;
+        }
         if (hasAction(DeviceDiscoveryAction.class)) {
             Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
             removeAction(DeviceDiscoveryAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index e6c2e7c..3ec3f94 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -137,7 +137,7 @@
                         // Since we removed all devices when it starts and device discovery action
                         // does not poll local devices, we should put device info of local device
                         // manually here.
-                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+                        for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) {
                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
                         }
 
@@ -190,7 +190,7 @@
         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
             return;
         }
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setActiveSource(targetDevice, "HdmiCecLocalDevicePlayback#deviceSelect()");
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -239,7 +239,7 @@
     @ServiceThreadOnly
     protected void onStandby(boolean initiatedByCec, int standbyAction) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             return;
         }
         boolean wasActiveSource = isActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 8a22ab9..96e7b03 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -259,7 +259,7 @@
             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
             return;
         }
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setActiveSource(targetDevice, "HdmiCecLocalDeviceTv#deviceSelect()");
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -272,7 +272,7 @@
     private void handleSelectInternalSource() {
         assertRunOnServiceThread();
         // Seq #18
-        if (mService.isControlEnabled()
+        if (mService.isCecControlEnabled()
                 && getActiveSource().logicalAddress != getDeviceInfo().getLogicalAddress()) {
             updateActiveSource(
                     getDeviceInfo().getLogicalAddress(),
@@ -371,7 +371,7 @@
             return;
         }
         getActiveSource().invalidate();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setActivePortId(portId);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -694,7 +694,7 @@
                         // Since we removed all devices when it starts and
                         // device discovery action does not poll local devices,
                         // we should put device info of local device manually here
-                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+                        for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) {
                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
                         }
 
@@ -742,7 +742,7 @@
     // Seq #32
     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
+        if (!mService.isCecControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
             setSystemAudioMode(false);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -1181,7 +1181,7 @@
     }
 
     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
-        return mService.isControlEnabled()
+        return mService.isCecControlEnabled()
                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
                 && (message.getDestination() == Constants.ADDR_TV
                         || message.getDestination() == Constants.ADDR_BROADCAST)
@@ -1330,7 +1330,7 @@
         removeAction(SystemAudioAutoInitiationAction.class);
         removeAction(VolumeControlAction.class);
 
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setSystemAudioMode(false);
         }
     }
@@ -1376,7 +1376,7 @@
     protected void onStandby(boolean initiatedByCec, int standbyAction) {
         assertRunOnServiceThread();
         // Seq #11
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             return;
         }
         boolean sendStandbyOnSleep =
@@ -1415,7 +1415,7 @@
     @Constants.HandleMessageResult
     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
             return Constants.ABORT_NOT_IN_CORRECT_MODE;
@@ -1444,7 +1444,7 @@
     @ServiceThreadOnly
     void stopOneTouchRecord(int recorderAddress) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
             return;
@@ -1478,7 +1478,7 @@
     @ServiceThreadOnly
     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
             announceTimerRecordingResult(recorderAddress,
                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
@@ -1514,7 +1514,7 @@
     @ServiceThreadOnly
     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
             return;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
index 552ff37..f819f00 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
@@ -75,7 +75,7 @@
     }
 
     private void sendReportPowerStatus(int powerStatus) {
-        for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllLocalDevices()) {
+        for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllCecLocalDevices()) {
             mHdmiControlService.sendCecCommand(
                     HdmiCecMessageBuilder.buildReportPowerStatus(
                             localDevice.getDeviceInfo().getLogicalAddress(),
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 1ae1b5b..43cd71a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -19,6 +19,8 @@
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
 import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
+import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
 
 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
 import static com.android.server.hdmi.Constants.DISABLED;
@@ -188,6 +190,7 @@
     static final int INITIATED_BY_SCREEN_ON = 2;
     static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
     static final int INITIATED_BY_HOTPLUG = 4;
+    static final int INITIATED_BY_SOUNDBAR_MODE = 5;
 
     // The reason code representing the intent action that drives the standby
     // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
@@ -336,8 +339,8 @@
     // Used to synchronize the access to the service.
     private final Object mLock = new Object();
 
-    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
-    private final List<Integer> mLocalDevices;
+    // Type of CEC logical devices hosted in the system. Stored in the unmodifiable list.
+    private final List<Integer> mCecLocalDevices;
 
     // List of records for HDMI control status change listener for death monitoring.
     @GuardedBy("mLock")
@@ -496,7 +499,7 @@
     @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
             AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
         super(context);
-        mLocalDevices = deviceTypes;
+        mCecLocalDevices = deviceTypes;
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
         mAudioDeviceVolumeManager = audioDeviceVolumeManager;
@@ -504,7 +507,7 @@
 
     public HdmiControlService(Context context) {
         super(context);
-        mLocalDevices = readDeviceTypes();
+        mCecLocalDevices = readDeviceTypes();
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
     }
@@ -666,7 +669,7 @@
                     public void onChange(String setting) {
                         @HdmiControlManager.HdmiCecControl int enabled = mHdmiCecConfig.getIntValue(
                                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
-                        setControlEnabled(enabled);
+                        setCecEnabled(enabled);
                     }
                 }, mServiceThreadExecutor);
         mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
@@ -693,6 +696,14 @@
                         }
                     }
                 }, mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        setSoundbarMode(mHdmiCecConfig.getIntValue(
+                                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE));
+                    }
+                }, mServiceThreadExecutor);
         mHdmiCecConfig.registerChangeListener(
                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
                 new HdmiCecConfig.SettingChangeListener() {
@@ -747,7 +758,7 @@
             mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
             // Start all actions that were queued because the device was in standby
             if (mAddressAllocated) {
-                for (HdmiCecLocalDevice localDevice : getAllLocalDevices()) {
+                for (HdmiCecLocalDevice localDevice : getAllCecLocalDevices()) {
                     localDevice.startQueuedActions();
                 }
             }
@@ -770,6 +781,11 @@
     }
 
     @VisibleForTesting
+    void setAudioManager(AudioManager audioManager) {
+        mAudioManager = audioManager;
+    }
+
+    @VisibleForTesting
     void setCecController(HdmiCecController cecController) {
         mCecController = cecController;
     }
@@ -847,6 +863,47 @@
     }
 
     /**
+     * Triggers the address allocation that states the presence of a local device audio system in
+     * the network.
+     */
+    @VisibleForTesting
+    public void setSoundbarMode(final int settingValue) {
+        HdmiCecLocalDevicePlayback playback = playback();
+        HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+        if (playback == null) {
+            Slog.w(TAG, "Device type not compatible to change soundbar mode.");
+            return;
+        }
+        if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+            Slog.w(TAG, "Device type doesn't support ARC.");
+            return;
+        }
+        if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
+            if (audioSystem.isArcEnabled()) {
+                audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem));
+            }
+            if (isSystemAudioActivated()) {
+                audioSystem.terminateSystemAudioMode();
+            }
+        }
+        mAddressAllocated = false;
+        initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+    }
+
+    /**
+     * Checks if the Device Discovery is handled by the local device playback.
+     * See {@link HdmiCecLocalDeviceAudioSystem#launchDeviceDiscovery}.
+     */
+    public boolean isDeviceDiscoveryHandledByPlayback() {
+        HdmiCecLocalDevicePlayback playback = playback();
+        if (playback != null && (playback.hasAction(DeviceDiscoveryAction.class)
+                || playback.hasAction(HotplugDetectionAction.class))) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Called when the initialization of local devices is complete.
      */
     private void onInitializeCecComplete(int initiatedBy) {
@@ -866,7 +923,7 @@
                 break;
             case INITIATED_BY_SCREEN_ON:
                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
-                final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
+                final List<HdmiCecLocalDevice> devices = getAllCecLocalDevices();
                 for (HdmiCecLocalDevice device : devices) {
                     device.onInitializeCecComplete(initiatedBy);
                 }
@@ -990,15 +1047,33 @@
 
         mCecController.enableSystemCecControl(true);
         mCecController.setLanguage(mMenuLanguage);
-        initializeLocalDevices(initiatedBy);
+        initializeCecLocalDevices(initiatedBy);
+    }
+
+    /**
+     * If the Soundbar mode is turned on, adds the local device type audio system in the list of
+     * local devices types. This method is called when the local devices are initialized such that
+     * the list of local devices is in sync with the Soundbar mode setting.
+     * @return the list of integer device types
+     */
+    @ServiceThreadOnly
+    private List<Integer> getCecLocalDeviceTypes() {
+        ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
+        if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                == SOUNDBAR_MODE_ENABLED
+                && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+            allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        }
+        return allLocalDeviceTypes;
     }
 
     @ServiceThreadOnly
-    private void initializeLocalDevices(final int initiatedBy) {
+    private void initializeCecLocalDevices(final int initiatedBy) {
         assertRunOnServiceThread();
         // A container for [Device type, Local device info].
         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
-        for (int type : mLocalDevices) {
+        for (int type : getCecLocalDeviceTypes()) {
             HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
             if (localDevice == null) {
                 localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1008,7 +1083,7 @@
         }
         // It's now safe to flush existing local devices from mCecController since they were
         // already moved to 'localDevices'.
-        clearLocalDevices();
+        clearCecLocalDevices();
         allocateLogicalAddress(localDevices, initiatedBy);
     }
 
@@ -1051,9 +1126,10 @@
 
                             // Address allocation completed for all devices. Notify each device.
                             if (allocatingDevices.size() == ++finished[0]) {
-                                if (initiatedBy != INITIATED_BY_HOTPLUG) {
-                                    // In case of the hotplug we don't call
-                                    // onInitializeCecComplete()
+                                if (initiatedBy != INITIATED_BY_HOTPLUG
+                                        && initiatedBy != INITIATED_BY_SOUNDBAR_MODE) {
+                                    // In case of the hotplug or soundbar mode setting toggle
+                                    // we don't call onInitializeCecComplete()
                                     // since we reallocate the logical address only.
                                     onInitializeCecComplete(initiatedBy);
                                 }
@@ -1331,7 +1407,7 @@
      * Returns whether the source address of a message is a local logical address.
      */
     private boolean sourceAddressIsLocal(HdmiCecMessage message) {
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
                     && message.getSource() != Constants.ADDR_UNREGISTERED) {
                 HdmiLogger.warning(
@@ -1413,7 +1489,7 @@
         if (connected && !isTvDevice()
                 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
-            for (int type : mLocalDevices) {
+            for (int type : getCecLocalDeviceTypes()) {
                 HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
                 if (localDevice == null) {
                     localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1461,7 +1537,7 @@
         return strategy | iterationStrategy;
     }
 
-    List<HdmiCecLocalDevice> getAllLocalDevices() {
+    List<HdmiCecLocalDevice> getAllCecLocalDevices() {
         assertRunOnServiceThread();
         return mHdmiCecNetwork.getLocalDeviceList();
     }
@@ -1484,7 +1560,7 @@
         if (physicalAddress == getPhysicalAddress()) {
             return;
         }
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) {
                 HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo());
                 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
@@ -1555,7 +1631,7 @@
     // Set the display name in HdmiDeviceInfo of the current devices to content provided by
     // Global.DEVICE_NAME. Only set and broadcast if the new name is different.
     private void setDisplayName(String newDisplayName) {
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             HdmiDeviceInfo deviceInfo = device.getDeviceInfo();
             if (deviceInfo.getDisplayName().equals(newDisplayName)) {
                 continue;
@@ -1816,10 +1892,10 @@
         @Override
         public int[] getSupportedTypes() {
             initBinderCall();
-            // mLocalDevices is an unmodifiable list - no lock necesary.
-            int[] localDevices = new int[mLocalDevices.size()];
+            // mCecLocalDevices is an unmodifiable list - no lock necessary.
+            int[] localDevices = new int[mCecLocalDevices.size()];
             for (int i = 0; i < localDevices.length; ++i) {
-                localDevices[i] = mLocalDevices.get(i);
+                localDevices[i] = mCecLocalDevices.get(i);
             }
             return localDevices;
         }
@@ -2379,7 +2455,7 @@
             runOnServiceThread(new Runnable() {
                 @Override
                 public void run() {
-                    if (!isControlEnabled()) {
+                    if (!isCecControlEnabled()) {
                         Slog.w(TAG, "Hdmi control is disabled.");
                         return ;
                     }
@@ -3172,15 +3248,15 @@
     }
 
     boolean isTvDevice() {
-        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
+        return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
     }
 
     boolean isAudioSystemDevice() {
-        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     }
 
     boolean isPlaybackDevice() {
-        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+        return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
     }
 
     boolean isSwitchDevice() {
@@ -3217,7 +3293,7 @@
         return mAudioDeviceVolumeManager;
     }
 
-    boolean isControlEnabled() {
+    boolean isCecControlEnabled() {
         synchronized (mLock) {
             return mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
         }
@@ -3329,7 +3405,7 @@
         invokeVendorCommandListenersOnControlStateChanged(false,
                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
 
-        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
+        final List<HdmiCecLocalDevice> devices = getAllCecLocalDevices();
 
         if (!isStandbyMessageReceived() && !canGoToStandby()) {
             mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
@@ -3339,7 +3415,7 @@
             return;
         }
 
-        disableDevices(new PendingActionClearedCallback() {
+        disableCecLocalDevices(new PendingActionClearedCallback() {
             @Override
             public void onCleared(HdmiCecLocalDevice device) {
                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
@@ -3387,7 +3463,7 @@
         return mMenuLanguage;
     }
 
-    private void disableDevices(PendingActionClearedCallback callback) {
+    private void disableCecLocalDevices(PendingActionClearedCallback callback) {
         if (mCecController != null) {
             for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
                 device.disableDevice(mStandbyMessageReceived, callback);
@@ -3397,7 +3473,8 @@
     }
 
     @ServiceThreadOnly
-    private void clearLocalDevices() {
+    @VisibleForTesting
+    protected void clearCecLocalDevices() {
         assertRunOnServiceThread();
         if (mCecController == null) {
             return;
@@ -3573,7 +3650,7 @@
     }
 
     @ServiceThreadOnly
-    void setControlEnabled(@HdmiControlManager.HdmiCecControl int enabled) {
+    void setCecEnabled(@HdmiControlManager.HdmiCecControl int enabled) {
         assertRunOnServiceThread();
 
         synchronized (mLock) {
@@ -3581,7 +3658,7 @@
         }
 
         if (enabled == HDMI_CEC_CONTROL_ENABLED) {
-            enableHdmiControlService();
+            onEnableCec();
             setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
                     HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
             return;
@@ -3596,7 +3673,7 @@
         runOnServiceThread(new Runnable() {
             @Override
             public void run() {
-                disableHdmiControlService();
+                onDisableCec();
             }
         });
         announceHdmiControlStatusChange(enabled);
@@ -3605,7 +3682,7 @@
     }
 
     @ServiceThreadOnly
-    private void enableHdmiControlService() {
+    private void onEnableCec() {
         mCecController.enableCec(true);
         mCecController.enableSystemCecControl(true);
         mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
@@ -3614,8 +3691,8 @@
     }
 
     @ServiceThreadOnly
-    private void disableHdmiControlService() {
-        disableDevices(
+    private void onDisableCec() {
+        disableCecLocalDevices(
                 new PendingActionClearedCallback() {
                     @Override
                     public void onCleared(HdmiCecLocalDevice device) {
@@ -3627,7 +3704,7 @@
                                         mCecController.enableCec(false);
                                         mCecController.enableSystemCecControl(false);
                                         mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
-                                        clearLocalDevices();
+                                        clearCecLocalDevices();
                                     }
                                 });
                     }
@@ -3671,7 +3748,7 @@
 
         // If the current device is a source device, check if the current Active Source matches
         // the local device info.
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             boolean deviceIsActiveSource =
                     logicalAddress == device.getDeviceInfo().getLogicalAddress()
                             && physicalAddress == getPhysicalAddress();
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index c83fa2d..c99a7a0 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -19,7 +19,13 @@
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.BatteryState;
 import android.hardware.input.IInputDeviceBatteryListener;
 import android.hardware.input.IInputDeviceBatteryState;
@@ -46,6 +52,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -74,6 +81,7 @@
     private final NativeInputManagerService mNative;
     private final Handler mHandler;
     private final UEventManager mUEventManager;
+    private final BluetoothBatteryManager mBluetoothBatteryManager;
 
     // Maps a pid to the registered listener record for that process. There can only be one battery
     // listener per process.
@@ -88,18 +96,23 @@
     private boolean mIsPolling = false;
     @GuardedBy("mLock")
     private boolean mIsInteractive = true;
+    @Nullable
+    @GuardedBy("mLock")
+    private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener;
 
     BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
-        this(context, nativeService, looper, new UEventManager() {});
+        this(context, nativeService, looper, new UEventManager() {},
+                new LocalBluetoothBatteryManager(context));
     }
 
     @VisibleForTesting
     BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
-            UEventManager uEventManager) {
+            UEventManager uEventManager, BluetoothBatteryManager bbm) {
         mContext = context;
         mNative = nativeService;
         mHandler = new Handler(looper);
         mUEventManager = uEventManager;
+        mBluetoothBatteryManager = bbm;
     }
 
     public void systemRunning() {
@@ -150,6 +163,7 @@
                 // This is the first listener that is monitoring this device.
                 monitor = new DeviceMonitor(deviceId);
                 mDeviceMonitors.put(deviceId, monitor);
+                updateBluetoothMonitoring();
             }
 
             if (DEBUG) {
@@ -202,25 +216,39 @@
         mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
     }
 
-    private String getInputDeviceName(int deviceId) {
+    private <R> R processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func) {
         final InputDevice device =
                 Objects.requireNonNull(mContext.getSystemService(InputManager.class))
                         .getInputDevice(deviceId);
-        return device != null ? device.getName() : "<none>";
+        return device == null ? defaultValue : func.apply(device);
+    }
+
+    private String getInputDeviceName(int deviceId) {
+        return processInputDevice(deviceId, "<none>" /*defaultValue*/, InputDevice::getName);
     }
 
     private boolean hasBattery(int deviceId) {
-        final InputDevice device =
-                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                        .getInputDevice(deviceId);
-        return device != null && device.hasBattery();
+        return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::hasBattery);
     }
 
     private boolean isUsiDevice(int deviceId) {
-        final InputDevice device =
-                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                        .getInputDevice(deviceId);
-        return device != null && device.supportsUsi();
+        return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::supportsUsi);
+    }
+
+    @Nullable
+    private BluetoothDevice getBluetoothDevice(int inputDeviceId) {
+        return getBluetoothDevice(mContext,
+                processInputDevice(inputDeviceId, null /*defaultValue*/,
+                        InputDevice::getBluetoothAddress));
+    }
+
+    @Nullable
+    private static BluetoothDevice getBluetoothDevice(Context context, String address) {
+        if (address == null) return null;
+        final BluetoothAdapter adapter =
+                Objects.requireNonNull(context.getSystemService(BluetoothManager.class))
+                        .getAdapter();
+        return adapter.getRemoteDevice(address);
     }
 
     @GuardedBy("mLock")
@@ -350,6 +378,17 @@
         }
     }
 
+    private void handleBluetoothBatteryLevelChange(long eventTime, String address) {
+        synchronized (mLock) {
+            final DeviceMonitor monitor = findIf(mDeviceMonitors, (m) ->
+                    (m.mBluetoothDevice != null
+                            && address.equals(m.mBluetoothDevice.getAddress())));
+            if (monitor != null) {
+                monitor.onBluetoothBatteryChanged(eventTime);
+            }
+        }
+    }
+
     /** Gets the current battery state of an input device. */
     public IInputDeviceBatteryState getBatteryState(int deviceId) {
         synchronized (mLock) {
@@ -475,17 +514,52 @@
                 isPresent ? mNative.getBatteryCapacity(deviceId) / 100.f : Float.NaN);
     }
 
+    // Queries the battery state of an input device from Bluetooth.
+    private State queryBatteryStateFromBluetooth(int deviceId, long updateTime,
+            @NonNull BluetoothDevice bluetoothDevice) {
+        final int level = mBluetoothBatteryManager.getBatteryLevel(bluetoothDevice.getAddress());
+        if (level == BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF
+                || level == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            return new State(deviceId);
+        }
+        return new State(deviceId, updateTime, true /*isPresent*/, BatteryState.STATUS_UNKNOWN,
+                level / 100.f);
+    }
+
+    private void updateBluetoothMonitoring() {
+        synchronized (mLock) {
+            if (anyOf(mDeviceMonitors, (m) -> m.mBluetoothDevice != null)) {
+                // At least one input device being monitored is connected over Bluetooth.
+                if (mBluetoothBatteryListener == null) {
+                    if (DEBUG) Slog.d(TAG, "Registering bluetooth battery listener");
+                    mBluetoothBatteryListener = this::handleBluetoothBatteryLevelChange;
+                    mBluetoothBatteryManager.addListener(mBluetoothBatteryListener);
+                }
+            } else if (mBluetoothBatteryListener != null) {
+                // No Bluetooth input devices are monitored, so remove the registered listener.
+                if (DEBUG) Slog.d(TAG, "Unregistering bluetooth battery listener");
+                mBluetoothBatteryManager.removeListener(mBluetoothBatteryListener);
+                mBluetoothBatteryListener = null;
+            }
+        }
+    }
+
     // Holds the state of an InputDevice for which battery changes are currently being monitored.
     private class DeviceMonitor {
         protected final State mState;
         // Represents whether the input device has a sysfs battery node.
         protected boolean mHasBattery = false;
 
+        protected final State mBluetoothState;
+        @Nullable
+        private BluetoothDevice mBluetoothDevice;
+
         @Nullable
         private UEventBatteryListener mUEventBatteryListener;
 
         DeviceMonitor(int deviceId) {
             mState = new State(deviceId);
+            mBluetoothState = new State(deviceId);
 
             // Load the initial battery state and start monitoring.
             final long eventTime = SystemClock.uptimeMillis();
@@ -506,18 +580,31 @@
         }
 
         private void configureDeviceMonitor(long eventTime) {
+            final int deviceId = mState.deviceId;
             if (mHasBattery != hasBattery(mState.deviceId)) {
                 mHasBattery = !mHasBattery;
                 if (mHasBattery) {
-                    startMonitoring();
+                    startNativeMonitoring();
                 } else {
-                    stopMonitoring();
+                    stopNativeMonitoring();
                 }
                 updateBatteryStateFromNative(eventTime);
             }
+
+            final BluetoothDevice bluetoothDevice = getBluetoothDevice(deviceId);
+            if (!Objects.equals(mBluetoothDevice, bluetoothDevice)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Bluetooth device "
+                            + ((bluetoothDevice != null) ? "is" : "is not")
+                            + " now present for deviceId " + deviceId);
+                }
+                mBluetoothDevice = bluetoothDevice;
+                updateBluetoothMonitoring();
+                updateBatteryStateFromBluetooth(eventTime);
+            }
         }
 
-        private void startMonitoring() {
+        private void startNativeMonitoring() {
             final String batteryPath = mNative.getBatteryDevicePath(mState.deviceId);
             if (batteryPath == null) {
                 return;
@@ -538,7 +625,7 @@
             return path.startsWith("/sys") ? path.substring(4) : path;
         }
 
-        private void stopMonitoring() {
+        private void stopNativeMonitoring() {
             if (mUEventBatteryListener != null) {
                 mUEventManager.removeListener(mUEventBatteryListener);
                 mUEventBatteryListener = null;
@@ -547,7 +634,9 @@
 
         // This must be called when the device is no longer being monitored.
         public void onMonitorDestroy() {
-            stopMonitoring();
+            stopNativeMonitoring();
+            mBluetoothDevice = null;
+            updateBluetoothMonitoring();
         }
 
         protected void updateBatteryStateFromNative(long eventTime) {
@@ -555,6 +644,13 @@
                     queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery));
         }
 
+        protected void updateBatteryStateFromBluetooth(long eventTime) {
+            final State bluetoothState = mBluetoothDevice == null ? new State(mState.deviceId)
+                    : queryBatteryStateFromBluetooth(mState.deviceId, eventTime,
+                            mBluetoothDevice);
+            mBluetoothState.updateIfChanged(bluetoothState);
+        }
+
         public void onPoll(long eventTime) {
             processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
         }
@@ -563,6 +659,10 @@
             processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
         }
 
+        public void onBluetoothBatteryChanged(long eventTime) {
+            processChangesAndNotify(eventTime, this::updateBatteryStateFromBluetooth);
+        }
+
         public boolean requiresPolling() {
             return true;
         }
@@ -577,6 +677,10 @@
 
         // Returns the current battery state that can be used to notify listeners BatteryController.
         public State getBatteryStateForReporting() {
+            // Give precedence to the Bluetooth battery state if it's present.
+            if (mBluetoothState.isPresent) {
+                return new State(mBluetoothState);
+            }
             return new State(mState);
         }
 
@@ -585,7 +689,8 @@
             return "DeviceId=" + mState.deviceId
                     + ", Name='" + getInputDeviceName(mState.deviceId) + "'"
                     + ", NativeBattery=" + mState
-                    + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
+                    + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none")
+                    + ", BluetoothBattery=" + mBluetoothState;
         }
     }
 
@@ -670,6 +775,10 @@
 
         @Override
         public State getBatteryStateForReporting() {
+            // Give precedence to the Bluetooth battery state if it's present.
+            if (mBluetoothState.isPresent) {
+                return new State(mBluetoothState);
+            }
             return mValidityTimeoutCallback != null
                     ? new State(mState) : new State(mState.deviceId);
         }
@@ -729,6 +838,82 @@
         }
     }
 
+    // An interface used to change the API of adding a bluetooth battery listener to a more
+    // test-friendly format.
+    @VisibleForTesting
+    interface BluetoothBatteryManager {
+        @VisibleForTesting
+        interface BluetoothBatteryListener {
+            void onBluetoothBatteryChanged(long eventTime, String address);
+        }
+        void addListener(BluetoothBatteryListener listener);
+        void removeListener(BluetoothBatteryListener listener);
+        int getBatteryLevel(String address);
+    }
+
+    private static class LocalBluetoothBatteryManager implements BluetoothBatteryManager {
+        private final Context mContext;
+        @Nullable
+        @GuardedBy("mBroadcastReceiver")
+        private BluetoothBatteryListener mRegisteredListener;
+        @GuardedBy("mBroadcastReceiver")
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED.equals(intent.getAction())) {
+                    return;
+                }
+                final BluetoothDevice bluetoothDevice = intent.getParcelableExtra(
+                        BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+                if (bluetoothDevice == null) {
+                    return;
+                }
+                // We do not use the EXTRA_LEVEL value. Instead, the battery level will be queried
+                // from BluetoothDevice later so that we use a single source for the battery level.
+                synchronized (mBroadcastReceiver) {
+                    if (mRegisteredListener != null) {
+                        final long eventTime = SystemClock.uptimeMillis();
+                        mRegisteredListener.onBluetoothBatteryChanged(
+                                eventTime, bluetoothDevice.getAddress());
+                    }
+                }
+            }
+        };
+
+        LocalBluetoothBatteryManager(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void addListener(BluetoothBatteryListener listener) {
+            synchronized (mBroadcastReceiver) {
+                if (mRegisteredListener != null) {
+                    throw new IllegalStateException(
+                            "Only one bluetooth battery listener can be registered at once.");
+                }
+                mRegisteredListener = listener;
+                mContext.registerReceiver(mBroadcastReceiver,
+                        new IntentFilter(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED));
+            }
+        }
+
+        @Override
+        public void removeListener(BluetoothBatteryListener listener) {
+            synchronized (mBroadcastReceiver) {
+                if (!listener.equals(mRegisteredListener)) {
+                    throw new IllegalStateException("Listener is not registered.");
+                }
+                mRegisteredListener = null;
+                mContext.unregisterReceiver(mBroadcastReceiver);
+            }
+        }
+
+        @Override
+        public int getBatteryLevel(String address) {
+            return getBluetoothDevice(mContext, address).getBatteryLevel();
+        }
+    }
+
     // Helper class that adds copying and printing functionality to IInputDeviceBatteryState.
     private static class State extends IInputDeviceBatteryState {
 
@@ -792,11 +977,17 @@
 
     // Check if any value in an ArrayMap matches the predicate in an optimized way.
     private static <K, V> boolean anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
+        return findIf(arrayMap, test) != null;
+    }
+
+    // Find the first value in an ArrayMap that matches the predicate in an optimized way.
+    private static <K, V> V findIf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
         for (int i = 0; i < arrayMap.size(); i++) {
-            if (test.test(arrayMap.valueAt(i))) {
-                return true;
+            final V value = arrayMap.valueAt(i);
+            if (test.test(value)) {
+                return value;
             }
         }
-        return false;
+        return null;
     }
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c20d880..199519c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -25,25 +25,13 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.graphics.PointF;
 import android.hardware.SensorPrivacyManager;
@@ -66,7 +54,6 @@
 import android.hardware.lights.LightState;
 import android.media.AudioManager;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.CombinedVibration;
 import android.os.Environment;
 import android.os.Handler;
@@ -75,7 +62,6 @@
 import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionResult;
 import android.os.InputEventInjectionSync;
-import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
@@ -113,18 +99,14 @@
 import android.view.VerifiedInputEvent;
 import android.view.ViewConfiguration;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.InputMethodSubtypeHandle;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
@@ -132,7 +114,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 
 import libcore.io.IoUtils;
-import libcore.io.Streams;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -141,15 +122,11 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
@@ -171,12 +148,9 @@
     private static final String VELOCITYTRACKER_STRATEGY_PROPERTY = "velocitytracker_strategy";
 
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
-    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
-    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
-    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
-    private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
-    private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
-    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
+    private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
+    private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -195,7 +169,6 @@
     private WindowManagerCallbacks mWindowManagerCallbacks;
     private WiredAccessoryCallbacks mWiredAccessoryCallbacks;
     private boolean mSystemReady;
-    private NotificationManager mNotificationManager;
 
     private final Object mTabletModeLock = new Object();
     // List of currently registered tablet mode changed listeners by process id
@@ -229,10 +202,6 @@
             new SparseArray<>();
     private final ArrayList<InputDevicesChangedListenerRecord>
             mTempInputDevicesChangedListenersToNotify = new ArrayList<>(); // handler thread only
-    private final ArrayList<InputDevice> mTempFullKeyboards =
-            new ArrayList<>(); // handler thread only
-    private boolean mKeyboardLayoutNotificationShown;
-    private Toast mSwitchedKeyboardLayoutToast;
 
     // State for vibrator tokens.
     private final Object mVibratorLock = new Object();
@@ -315,6 +284,9 @@
     @GuardedBy("mInputMonitors")
     final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
 
+    // Manages Keyboard layouts for Physical keyboards
+    private final KeyboardLayoutManager mKeyboardLayoutManager;
+
     // Manages battery state for input devices.
     private final BatteryController mBatteryController;
 
@@ -430,6 +402,8 @@
         mContext = injector.getContext();
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
+        mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
+                injector.getLooper());
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
         mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
                 mDataStore, injector.getLooper());
@@ -518,8 +492,6 @@
         if (DEBUG) {
             Slog.d(TAG, "System ready.");
         }
-        mNotificationManager = (NotificationManager)mContext.getSystemService(
-                Context.NOTIFICATION_SERVICE);
 
         synchronized (mLidSwitchLock) {
             mSystemReady = true;
@@ -546,19 +518,7 @@
             setSensorPrivacy(Sensors.CAMERA, true);
         }
 
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        filter.addDataScheme("package");
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                updateKeyboardLayouts();
-            }
-        }, filter, null, mHandler);
-
-        filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
+        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -567,23 +527,16 @@
         }, filter, null, mHandler);
 
         mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
-        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
 
         if (mWiredAccessoryCallbacks != null) {
             mWiredAccessoryCallbacks.systemReady();
         }
 
+        mKeyboardLayoutManager.systemRunning();
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
     }
 
-    private void reloadKeyboardLayouts() {
-        if (DEBUG) {
-            Slog.d(TAG, "Reloading keyboard layouts.");
-        }
-        mNative.reloadKeyboardLayouts();
-    }
-
     private void reloadDeviceAliases() {
         if (DEBUG) {
             Slog.d(TAG, "Reloading device names.");
@@ -1044,9 +997,7 @@
     // Must be called on handler.
     private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
         // Scan for changes.
-        int numFullKeyboardsAdded = 0;
         mTempInputDevicesChangedListenersToNotify.clear();
-        mTempFullKeyboards.clear();
         final int numListeners;
         final int[] deviceIdAndGeneration;
         synchronized (mInputDevicesLock) {
@@ -1071,15 +1022,6 @@
                     Log.d(TAG, "device " + inputDevice.getId() + " generation "
                             + inputDevice.getGeneration());
                 }
-
-                if (!inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
-                    if (!containsInputDeviceWithDescriptor(oldInputDevices,
-                            inputDevice.getDescriptor())) {
-                        mTempFullKeyboards.add(numFullKeyboardsAdded++, inputDevice);
-                    } else {
-                        mTempFullKeyboards.add(inputDevice);
-                    }
-                }
             }
         }
 
@@ -1089,119 +1031,6 @@
                     deviceIdAndGeneration);
         }
         mTempInputDevicesChangedListenersToNotify.clear();
-
-        // Check for missing keyboard layouts.
-        List<InputDevice> keyboardsMissingLayout = new ArrayList<>();
-        final int numFullKeyboards = mTempFullKeyboards.size();
-        synchronized (mDataStore) {
-            for (int i = 0; i < numFullKeyboards; i++) {
-                final InputDevice inputDevice = mTempFullKeyboards.get(i);
-                String layout =
-                    getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
-                if (layout == null) {
-                    layout = getDefaultKeyboardLayout(inputDevice);
-                    if (layout != null) {
-                        setCurrentKeyboardLayoutForInputDevice(
-                                inputDevice.getIdentifier(), layout);
-                    }
-                }
-                if (layout == null) {
-                    keyboardsMissingLayout.add(inputDevice);
-                }
-            }
-        }
-
-        if (mNotificationManager != null) {
-            if (!keyboardsMissingLayout.isEmpty()) {
-                if (keyboardsMissingLayout.size() > 1) {
-                    // We have more than one keyboard missing a layout, so drop the
-                    // user at the generic input methods page so they can pick which
-                    // one to set.
-                    showMissingKeyboardLayoutNotification(null);
-                } else {
-                    showMissingKeyboardLayoutNotification(keyboardsMissingLayout.get(0));
-                }
-            } else if (mKeyboardLayoutNotificationShown) {
-                hideMissingKeyboardLayoutNotification();
-            }
-        }
-        mTempFullKeyboards.clear();
-    }
-
-    private String getDefaultKeyboardLayout(final InputDevice d) {
-        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
-        // If our locale doesn't have a language for some reason, then we don't really have a
-        // reasonable default.
-        if (TextUtils.isEmpty(systemLocale.getLanguage())) {
-            return null;
-        }
-        final List<KeyboardLayout> layouts = new ArrayList<>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
-            // Only select a default when we know the layout is appropriate. For now, this
-            // means it's a custom layout for a specific keyboard.
-            if (layout.getVendorId() != d.getVendorId()
-                    || layout.getProductId() != d.getProductId()) {
-                return;
-            }
-            final LocaleList locales = layout.getLocales();
-            final int numLocales = locales.size();
-            for (int localeIndex = 0; localeIndex < numLocales; ++localeIndex) {
-                if (isCompatibleLocale(systemLocale, locales.get(localeIndex))) {
-                    layouts.add(layout);
-                    break;
-                }
-            }
-        });
-
-        if (layouts.isEmpty()) {
-            return null;
-        }
-
-        // First sort so that ones with higher priority are listed at the top
-        Collections.sort(layouts);
-        // Next we want to try to find an exact match of language, country and variant.
-        final int N = layouts.size();
-        for (int i = 0; i < N; i++) {
-            KeyboardLayout layout = layouts.get(i);
-            final LocaleList locales = layout.getLocales();
-            final int numLocales = locales.size();
-            for (int localeIndex = 0; localeIndex < numLocales; ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale.getCountry().equals(systemLocale.getCountry())
-                        && locale.getVariant().equals(systemLocale.getVariant())) {
-                    return layout.getDescriptor();
-                }
-            }
-        }
-        // Then try an exact match of language and country
-        for (int i = 0; i < N; i++) {
-            KeyboardLayout layout = layouts.get(i);
-            final LocaleList locales = layout.getLocales();
-            final int numLocales = locales.size();
-            for (int localeIndex = 0; localeIndex < numLocales; ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale.getCountry().equals(systemLocale.getCountry())) {
-                    return layout.getDescriptor();
-                }
-            }
-        }
-
-        // Give up and just use the highest priority layout with matching language
-        return layouts.get(0).getDescriptor();
-    }
-
-    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
-        // Different languages are never compatible
-        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
-            return false;
-        }
-        // If both the system and the keyboard layout have a country specifier, they must be equal.
-        if (!TextUtils.isEmpty(systemLocale.getCountry())
-                && !TextUtils.isEmpty(keyboardLocale.getCountry())
-                && !systemLocale.getCountry().equals(keyboardLocale.getCountry())) {
-            return false;
-        }
-        return true;
     }
 
     @Override // Binder call & native callback
@@ -1302,446 +1131,61 @@
         }
     }
 
-    // Must be called on handler.
-    private void showMissingKeyboardLayoutNotification(InputDevice device) {
-        if (!mKeyboardLayoutNotificationShown) {
-            final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
-            if (device != null) {
-                intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
-            }
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
-                    intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
-
-            Resources r = mContext.getResources();
-            Notification notification =
-                    new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
-                            .setContentTitle(r.getString(
-                                    R.string.select_keyboard_layout_notification_title))
-                            .setContentText(r.getString(
-                                    R.string.select_keyboard_layout_notification_message))
-                            .setContentIntent(keyboardLayoutIntent)
-                            .setSmallIcon(R.drawable.ic_settings_language)
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .build();
-            mNotificationManager.notifyAsUser(null,
-                    SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
-                    notification, UserHandle.ALL);
-            mKeyboardLayoutNotificationShown = true;
-        }
-    }
-
-    // Must be called on handler.
-    private void hideMissingKeyboardLayoutNotification() {
-        if (mKeyboardLayoutNotificationShown) {
-            mKeyboardLayoutNotificationShown = false;
-            mNotificationManager.cancelAsUser(null,
-                    SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
-                    UserHandle.ALL);
-        }
-    }
-
-    // Must be called on handler.
-    private void updateKeyboardLayouts() {
-        // Scan all input devices state for keyboard layouts that have been uninstalled.
-        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
-                availableKeyboardLayouts.add(layout.getDescriptor()));
-        synchronized (mDataStore) {
-            try {
-                mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
-
-        // Reload keyboard layouts.
-        reloadKeyboardLayouts();
-    }
-
-    private static boolean containsInputDeviceWithDescriptor(InputDevice[] inputDevices,
-            String descriptor) {
-        final int numDevices = inputDevices.length;
-        for (int i = 0; i < numDevices; i++) {
-            final InputDevice inputDevice = inputDevices[i];
-            if (inputDevice.getDescriptor().equals(descriptor)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @Override // Binder call
     public KeyboardLayout[] getKeyboardLayouts() {
-        final ArrayList<KeyboardLayout> list = new ArrayList<>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
-        return list.toArray(new KeyboardLayout[list.size()]);
+        return mKeyboardLayoutManager.getKeyboardLayouts();
     }
 
     @Override // Binder call
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             final InputDeviceIdentifier identifier) {
-        final String[] enabledLayoutDescriptors =
-                getEnabledKeyboardLayoutsForInputDevice(identifier);
-        final ArrayList<KeyboardLayout> enabledLayouts =
-                new ArrayList<>(enabledLayoutDescriptors.length);
-        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
-        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
-            boolean mHasSeenDeviceSpecificLayout;
-
-            @Override
-            public void visitKeyboardLayout(Resources resources,
-                    int keyboardLayoutResId, KeyboardLayout layout) {
-                // First check if it's enabled. If the keyboard layout is enabled then we always
-                // want to return it as a possible layout for the device.
-                for (String s : enabledLayoutDescriptors) {
-                    if (s != null && s.equals(layout.getDescriptor())) {
-                        enabledLayouts.add(layout);
-                        return;
-                    }
-                }
-                // Next find any potential layouts that aren't yet enabled for the device. For
-                // devices that have special layouts we assume there's a reason that the generic
-                // layouts don't work for them so we don't want to return them since it's likely
-                // to result in a poor user experience.
-                if (layout.getVendorId() == identifier.getVendorId()
-                        && layout.getProductId() == identifier.getProductId()) {
-                    if (!mHasSeenDeviceSpecificLayout) {
-                        mHasSeenDeviceSpecificLayout = true;
-                        potentialLayouts.clear();
-                    }
-                    potentialLayouts.add(layout);
-                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
-                        && !mHasSeenDeviceSpecificLayout) {
-                    potentialLayouts.add(layout);
-                }
-            }
-        });
-        final int enabledLayoutSize = enabledLayouts.size();
-        final int potentialLayoutSize = potentialLayouts.size();
-        KeyboardLayout[] layouts = new KeyboardLayout[enabledLayoutSize + potentialLayoutSize];
-        enabledLayouts.toArray(layouts);
-        for (int i = 0; i < potentialLayoutSize; i++) {
-            layouts[enabledLayoutSize + i] = potentialLayouts.get(i);
-        }
-        return layouts;
+        return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier);
     }
 
     @Override // Binder call
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        final KeyboardLayout[] result = new KeyboardLayout[1];
-        visitKeyboardLayout(keyboardLayoutDescriptor,
-                (resources, keyboardLayoutResId, layout) -> result[0] = layout);
-        if (result[0] == null) {
-            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
-                    + keyboardLayoutDescriptor + "'.");
-        }
-        return result[0];
-    }
-
-    private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
-        final PackageManager pm = mContext.getPackageManager();
-        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
-        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
-                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE)) {
-            final ActivityInfo activityInfo = resolveInfo.activityInfo;
-            final int priority = resolveInfo.priority;
-            visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
-        }
-    }
-
-    private void visitKeyboardLayout(String keyboardLayoutDescriptor,
-            KeyboardLayoutVisitor visitor) {
-        KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor);
-        if (d != null) {
-            final PackageManager pm = mContext.getPackageManager();
-            try {
-                ActivityInfo receiver = pm.getReceiverInfo(
-                        new ComponentName(d.packageName, d.receiverName),
-                        PackageManager.GET_META_DATA
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-                visitKeyboardLayoutsInPackage(pm, receiver, d.keyboardLayoutName, 0, visitor);
-            } catch (NameNotFoundException ignored) {
-            }
-        }
-    }
-
-    private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver,
-            String keyboardName, int requestedPriority, KeyboardLayoutVisitor visitor) {
-        Bundle metaData = receiver.metaData;
-        if (metaData == null) {
-            return;
-        }
-
-        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
-        if (configResId == 0) {
-            Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
-                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
-            return;
-        }
-
-        CharSequence receiverLabel = receiver.loadLabel(pm);
-        String collection = receiverLabel != null ? receiverLabel.toString() : "";
-        int priority;
-        if ((receiver.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-            priority = requestedPriority;
-        } else {
-            priority = 0;
-        }
-
-        try {
-            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
-            try (XmlResourceParser parser = resources.getXml(configResId)) {
-                XmlUtils.beginDocument(parser, "keyboard-layouts");
-
-                while (true) {
-                    XmlUtils.nextElement(parser);
-                    String element = parser.getName();
-                    if (element == null) {
-                        break;
-                    }
-                    if (element.equals("keyboard-layout")) {
-                        TypedArray a = resources.obtainAttributes(
-                                parser, R.styleable.KeyboardLayout);
-                        try {
-                            String name = a.getString(
-                                    R.styleable.KeyboardLayout_name);
-                            String label = a.getString(
-                                    R.styleable.KeyboardLayout_label);
-                            int keyboardLayoutResId = a.getResourceId(
-                                    R.styleable.KeyboardLayout_keyboardLayout,
-                                    0);
-                            String languageTags = a.getString(
-                                    R.styleable.KeyboardLayout_locale);
-                            LocaleList locales = getLocalesFromLanguageTags(languageTags);
-                            int vid = a.getInt(
-                                    R.styleable.KeyboardLayout_vendorId, -1);
-                            int pid = a.getInt(
-                                    R.styleable.KeyboardLayout_productId, -1);
-
-                            if (name == null || label == null || keyboardLayoutResId == 0) {
-                                Slog.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
-                                        + "attributes in keyboard layout "
-                                        + "resource from receiver "
-                                        + receiver.packageName + "/" + receiver.name);
-                            } else {
-                                String descriptor = KeyboardLayoutDescriptor.format(
-                                        receiver.packageName, receiver.name, name);
-                                if (keyboardName == null || name.equals(keyboardName)) {
-                                    KeyboardLayout layout = new KeyboardLayout(
-                                            descriptor, label, collection, priority,
-                                            locales, vid, pid);
-                                    visitor.visitKeyboardLayout(
-                                            resources, keyboardLayoutResId, layout);
-                                }
-                            }
-                        } finally {
-                            a.recycle();
-                        }
-                    } else {
-                        Slog.w(TAG, "Skipping unrecognized element '" + element
-                                + "' in keyboard layout resource from receiver "
-                                + receiver.packageName + "/" + receiver.name);
-                    }
-                }
-            }
-        } catch (Exception ex) {
-            Slog.w(TAG, "Could not parse keyboard layout resource from receiver "
-                    + receiver.packageName + "/" + receiver.name, ex);
-        }
-    }
-
-    @NonNull
-    private static LocaleList getLocalesFromLanguageTags(String languageTags) {
-        if (TextUtils.isEmpty(languageTags)) {
-            return LocaleList.getEmptyLocaleList();
-        }
-        return LocaleList.forLanguageTags(languageTags.replace('|', ','));
-    }
-
-    /**
-     * Builds a layout descriptor for the vendor/product. This returns the
-     * descriptor for ids that aren't useful (such as the default 0, 0).
-     */
-    private String getLayoutDescriptor(InputDeviceIdentifier identifier) {
-        Objects.requireNonNull(identifier, "identifier must not be null");
-        Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
-
-        if (identifier.getVendorId() == 0 && identifier.getProductId() == 0) {
-            return identifier.getDescriptor();
-        }
-        return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
+        return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor);
     }
 
     @Override // Binder call
     public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            String layout;
-            // try loading it using the layout descriptor if we have it
-            layout = mDataStore.getCurrentKeyboardLayout(key);
-            if (layout == null && !key.equals(identifier.getDescriptor())) {
-                // if it doesn't exist fall back to the device descriptor
-                layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
-                        + identifier.toString() + ": " + layout);
-            }
-            return layout;
-        }
+        return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier);
     }
 
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     @Override // Binder call
     public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "setCurrentKeyboardLayoutForInputDevice()")) {
-            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
-        }
-
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            try {
-                if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
-                                + " key: " + key
-                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
-                    }
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
+        super.setCurrentKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier,
+                keyboardLayoutDescriptor);
     }
 
     @Override // Binder call
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            String[] layouts = mDataStore.getKeyboardLayouts(key);
-            if ((layouts == null || layouts.length == 0)
-                    && !key.equals(identifier.getDescriptor())) {
-                layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
-            }
-            return layouts;
-        }
+        return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier);
     }
 
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     @Override // Binder call
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "addKeyboardLayoutForInputDevice()")) {
-            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
-        }
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            try {
-                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
-                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
-                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-                }
-                if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
-                        && !Objects.equals(oldLayout,
-                                mDataStore.getCurrentKeyboardLayout(key))) {
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
+        super.addKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier,
+                keyboardLayoutDescriptor);
     }
 
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     @Override // Binder call
     public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "removeKeyboardLayoutForInputDevice()")) {
-            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
-        }
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            try {
-                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
-                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
-                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-                }
-                boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
-                if (!key.equals(identifier.getDescriptor())) {
-                    // We need to remove from both places to ensure it is gone
-                    removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
-                            keyboardLayoutDescriptor);
-                }
-                if (removed && !Objects.equals(oldLayout,
-                                mDataStore.getCurrentKeyboardLayout(key))) {
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
+        super.removeKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier,
+                keyboardLayoutDescriptor);
     }
 
     public void switchKeyboardLayout(int deviceId, int direction) {
-        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
-    }
-
-    // Must be called on handler.
-    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
-        final InputDevice device = getInputDevice(deviceId);
-        if (device != null) {
-            final boolean changed;
-            final String keyboardLayoutDescriptor;
-
-            String key = getLayoutDescriptor(device.getIdentifier());
-            synchronized (mDataStore) {
-                try {
-                    changed = mDataStore.switchKeyboardLayout(key, direction);
-                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
-                            key);
-                } finally {
-                    mDataStore.saveIfNeeded();
-                }
-            }
-
-            if (changed) {
-                if (mSwitchedKeyboardLayoutToast != null) {
-                    mSwitchedKeyboardLayoutToast.cancel();
-                    mSwitchedKeyboardLayoutToast = null;
-                }
-                if (keyboardLayoutDescriptor != null) {
-                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
-                    if (keyboardLayout != null) {
-                        mSwitchedKeyboardLayoutToast = Toast.makeText(
-                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
-                        mSwitchedKeyboardLayoutToast.show();
-                    }
-                }
-
-                reloadKeyboardLayouts();
-            }
-        }
+        mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
     }
 
     public void setFocusedApplication(int displayId, InputApplicationHandle application) {
@@ -2682,7 +2126,13 @@
     public String getInputDeviceBluetoothAddress(int deviceId) {
         super.getInputDeviceBluetoothAddress_enforcePermission();
 
-        return mNative.getBluetoothAddress(deviceId);
+        final String address = mNative.getBluetoothAddress(deviceId);
+        if (address == null) return null;
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            throw new IllegalStateException("The Bluetooth address of input device " + deviceId
+                    + " should not be invalid: address=" + address);
+        }
+        return address;
     }
 
     @EnforcePermission(Manifest.permission.MONITOR_INPUT)
@@ -2770,6 +2220,11 @@
             if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
                 pw.println("mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId);
             }
+
+            pw.println("mAcknowledgedPointerDisplayId=" + mAcknowledgedPointerDisplayId);
+            pw.println("mRequestedPointerDisplayId=" + mRequestedPointerDisplayId);
+            pw.println("mPointerIconType=" + PointerIcon.typeToString(mPointerIconType));
+            pw.println("mPointerIcon=" + mPointerIcon);
         }
     }
     private boolean checkCallingPermission(String permission, String func) {
@@ -3252,28 +2707,7 @@
         if (!mSystemReady) {
             return null;
         }
-
-        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
-        if (keyboardLayoutDescriptor == null) {
-            return null;
-        }
-
-        final String[] result = new String[2];
-        visitKeyboardLayout(keyboardLayoutDescriptor,
-                (resources, keyboardLayoutResId, layout) -> {
-                    try (InputStreamReader stream = new InputStreamReader(
-                            resources.openRawResource(keyboardLayoutResId))) {
-                        result[0] = layout.getDescriptor();
-                        result[1] = Streams.readFully(stream);
-                    } catch (IOException | NotFoundException ignored) {
-                    }
-                });
-        if (result[0] == null) {
-            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
-                    + keyboardLayoutDescriptor + "'.");
-            return null;
-        }
-        return result;
+        return mKeyboardLayoutManager.getKeyboardLayoutOverlay(identifier);
     }
 
     // Native callback.
@@ -3471,15 +2905,6 @@
                 case MSG_DELIVER_INPUT_DEVICES_CHANGED:
                     deliverInputDevicesChanged((InputDevice[])msg.obj);
                     break;
-                case MSG_SWITCH_KEYBOARD_LAYOUT:
-                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
-                    break;
-                case MSG_RELOAD_KEYBOARD_LAYOUTS:
-                    reloadKeyboardLayouts();
-                    break;
-                case MSG_UPDATE_KEYBOARD_LAYOUTS:
-                    updateKeyboardLayouts();
-                    break;
                 case MSG_RELOAD_DEVICE_ALIASES:
                     reloadDeviceAliases();
                     break;
@@ -3548,39 +2973,6 @@
         }
     }
 
-    private static final class KeyboardLayoutDescriptor {
-        public String packageName;
-        public String receiverName;
-        public String keyboardLayoutName;
-
-        public static String format(String packageName,
-                String receiverName, String keyboardName) {
-            return packageName + "/" + receiverName + "/" + keyboardName;
-        }
-
-        public static KeyboardLayoutDescriptor parse(String descriptor) {
-            int pos = descriptor.indexOf('/');
-            if (pos < 0 || pos + 1 == descriptor.length()) {
-                return null;
-            }
-            int pos2 = descriptor.indexOf('/', pos + 1);
-            if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
-                return null;
-            }
-
-            KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
-            result.packageName = descriptor.substring(0, pos);
-            result.receiverName = descriptor.substring(pos + 1, pos2);
-            result.keyboardLayoutName = descriptor.substring(pos2 + 1);
-            return result;
-        }
-    }
-
-    private interface KeyboardLayoutVisitor {
-        void visitKeyboardLayout(Resources resources,
-                int keyboardLayoutResId, KeyboardLayout layout);
-    }
-
     private final class InputDevicesChangedListenerRecord implements DeathRecipient {
         private final int mPid;
         private final IInputDevicesChangedListener mListener;
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
new file mode 100644
index 0000000..fac001e
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -0,0 +1,736 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardLayout;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InputDevice;
+import android.widget.Toast;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts.
+ *
+ * @hide
+ */
+final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
+
+    private static final String TAG = "KeyboardLayoutManager";
+
+    // To enable these logs, run: 'adb shell setprop log.tag.KeyboardLayoutManager DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 1;
+    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2;
+    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3;
+
+    private final Context mContext;
+    private final NativeInputManagerService mNative;
+    // The PersistentDataStore should be locked before use.
+    @GuardedBy("mDataStore")
+    private final PersistentDataStore mDataStore;
+    private final Handler mHandler;
+    private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
+    private boolean mKeyboardLayoutNotificationShown = false;
+    private Toast mSwitchedKeyboardLayoutToast;
+
+    KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
+            PersistentDataStore dataStore, Looper looper) {
+        mContext = context;
+        mNative = nativeService;
+        mDataStore = dataStore;
+        mHandler = new Handler(looper, this::handleMessage, true /* async */);
+    }
+
+    public void systemRunning() {
+        // Listen to new Package installations to fetch new Keyboard layouts
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                updateKeyboardLayouts();
+            }
+        }, filter, null, mHandler);
+
+        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
+
+        // Listen to new InputDevice changes
+        InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        inputManager.registerInputDeviceListener(this, mHandler);
+        // Circle through all the already added input devices
+        for (int deviceId : inputManager.getInputDeviceIds()) {
+            onInputDeviceAdded(deviceId);
+        }
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        onInputDeviceChanged(deviceId);
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
+        maybeUpdateNotification();
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        final InputDevice inputDevice = getInputDevice(deviceId);
+        if (inputDevice == null) {
+            return;
+        }
+        synchronized (mDataStore) {
+            String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
+            if (layout == null) {
+                layout = getDefaultKeyboardLayout(inputDevice);
+                if (layout != null) {
+                    setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
+                } else {
+                    mKeyboardsWithMissingLayouts.add(inputDevice);
+                }
+            }
+            maybeUpdateNotification();
+        }
+    }
+
+    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
+        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
+        // If our locale doesn't have a language for some reason, then we don't really have a
+        // reasonable default.
+        if (TextUtils.isEmpty(systemLocale.getLanguage())) {
+            return null;
+        }
+        final List<KeyboardLayout> layouts = new ArrayList<>();
+        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
+            // Only select a default when we know the layout is appropriate. For now, this
+            // means it's a custom layout for a specific keyboard.
+            if (layout.getVendorId() != inputDevice.getVendorId()
+                    || layout.getProductId() != inputDevice.getProductId()) {
+                return;
+            }
+            final LocaleList locales = layout.getLocales();
+            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
+                final Locale locale = locales.get(localeIndex);
+                if (locale != null && isCompatibleLocale(systemLocale, locale)) {
+                    layouts.add(layout);
+                    break;
+                }
+            }
+        });
+
+        if (layouts.isEmpty()) {
+            return null;
+        }
+
+        // First sort so that ones with higher priority are listed at the top
+        Collections.sort(layouts);
+        // Next we want to try to find an exact match of language, country and variant.
+        for (KeyboardLayout layout : layouts) {
+            final LocaleList locales = layout.getLocales();
+            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
+                final Locale locale = locales.get(localeIndex);
+                if (locale != null && locale.getCountry().equals(systemLocale.getCountry())
+                        && locale.getVariant().equals(systemLocale.getVariant())) {
+                    return layout.getDescriptor();
+                }
+            }
+        }
+        // Then try an exact match of language and country
+        for (KeyboardLayout layout : layouts) {
+            final LocaleList locales = layout.getLocales();
+            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
+                final Locale locale = locales.get(localeIndex);
+                if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) {
+                    return layout.getDescriptor();
+                }
+            }
+        }
+
+        // Give up and just use the highest priority layout with matching language
+        return layouts.get(0).getDescriptor();
+    }
+
+    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
+        // Different languages are never compatible
+        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
+            return false;
+        }
+        // If both the system and the keyboard layout have a country specifier, they must be equal.
+        return TextUtils.isEmpty(systemLocale.getCountry())
+                || TextUtils.isEmpty(keyboardLocale.getCountry())
+                || systemLocale.getCountry().equals(keyboardLocale.getCountry());
+    }
+
+    private void updateKeyboardLayouts() {
+        // Scan all input devices state for keyboard layouts that have been uninstalled.
+        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
+                availableKeyboardLayouts.add(layout.getDescriptor()));
+        synchronized (mDataStore) {
+            try {
+                mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+
+        // Reload keyboard layouts.
+        reloadKeyboardLayouts();
+    }
+
+    public KeyboardLayout[] getKeyboardLayouts() {
+        final ArrayList<KeyboardLayout> list = new ArrayList<>();
+        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
+        return list.toArray(new KeyboardLayout[0]);
+    }
+
+    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
+            final InputDeviceIdentifier identifier) {
+        final String[] enabledLayoutDescriptors =
+                getEnabledKeyboardLayoutsForInputDevice(identifier);
+        final ArrayList<KeyboardLayout> enabledLayouts =
+                new ArrayList<>(enabledLayoutDescriptors.length);
+        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            boolean mHasSeenDeviceSpecificLayout;
+
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                // First check if it's enabled. If the keyboard layout is enabled then we always
+                // want to return it as a possible layout for the device.
+                for (String s : enabledLayoutDescriptors) {
+                    if (s != null && s.equals(layout.getDescriptor())) {
+                        enabledLayouts.add(layout);
+                        return;
+                    }
+                }
+                // Next find any potential layouts that aren't yet enabled for the device. For
+                // devices that have special layouts we assume there's a reason that the generic
+                // layouts don't work for them so we don't want to return them since it's likely
+                // to result in a poor user experience.
+                if (layout.getVendorId() == identifier.getVendorId()
+                        && layout.getProductId() == identifier.getProductId()) {
+                    if (!mHasSeenDeviceSpecificLayout) {
+                        mHasSeenDeviceSpecificLayout = true;
+                        potentialLayouts.clear();
+                    }
+                    potentialLayouts.add(layout);
+                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
+                        && !mHasSeenDeviceSpecificLayout) {
+                    potentialLayouts.add(layout);
+                }
+            }
+        });
+        return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray(
+                KeyboardLayout[]::new);
+    }
+
+    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        final KeyboardLayout[] result = new KeyboardLayout[1];
+        visitKeyboardLayout(keyboardLayoutDescriptor,
+                (resources, keyboardLayoutResId, layout) -> result[0] = layout);
+        if (result[0] == null) {
+            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+        }
+        return result[0];
+    }
+
+    private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
+        final PackageManager pm = mContext.getPackageManager();
+        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
+        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
+                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE)) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            final int priority = resolveInfo.priority;
+            visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
+        }
+    }
+
+    private void visitKeyboardLayout(String keyboardLayoutDescriptor,
+            KeyboardLayoutVisitor visitor) {
+        KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor);
+        if (d != null) {
+            final PackageManager pm = mContext.getPackageManager();
+            try {
+                ActivityInfo receiver = pm.getReceiverInfo(
+                        new ComponentName(d.packageName, d.receiverName),
+                        PackageManager.GET_META_DATA
+                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+                visitKeyboardLayoutsInPackage(pm, receiver, d.keyboardLayoutName, 0, visitor);
+            } catch (PackageManager.NameNotFoundException ignored) {
+            }
+        }
+    }
+
+    private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver,
+            String keyboardName, int requestedPriority, KeyboardLayoutVisitor visitor) {
+        Bundle metaData = receiver.metaData;
+        if (metaData == null) {
+            return;
+        }
+
+        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
+        if (configResId == 0) {
+            Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
+                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
+            return;
+        }
+
+        CharSequence receiverLabel = receiver.loadLabel(pm);
+        String collection = receiverLabel != null ? receiverLabel.toString() : "";
+        int priority;
+        if ((receiver.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            priority = requestedPriority;
+        } else {
+            priority = 0;
+        }
+
+        try {
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            try (XmlResourceParser parser = resources.getXml(configResId)) {
+                XmlUtils.beginDocument(parser, "keyboard-layouts");
+
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals("keyboard-layout")) {
+                        TypedArray a = resources.obtainAttributes(
+                                parser, R.styleable.KeyboardLayout);
+                        try {
+                            String name = a.getString(
+                                    R.styleable.KeyboardLayout_name);
+                            String label = a.getString(
+                                    R.styleable.KeyboardLayout_label);
+                            int keyboardLayoutResId = a.getResourceId(
+                                    R.styleable.KeyboardLayout_keyboardLayout,
+                                    0);
+                            String languageTags = a.getString(
+                                    R.styleable.KeyboardLayout_locale);
+                            LocaleList locales = getLocalesFromLanguageTags(languageTags);
+                            int vid = a.getInt(
+                                    R.styleable.KeyboardLayout_vendorId, -1);
+                            int pid = a.getInt(
+                                    R.styleable.KeyboardLayout_productId, -1);
+
+                            if (name == null || label == null || keyboardLayoutResId == 0) {
+                                Slog.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
+                                        + "attributes in keyboard layout "
+                                        + "resource from receiver "
+                                        + receiver.packageName + "/" + receiver.name);
+                            } else {
+                                String descriptor = KeyboardLayoutDescriptor.format(
+                                        receiver.packageName, receiver.name, name);
+                                if (keyboardName == null || name.equals(keyboardName)) {
+                                    KeyboardLayout layout = new KeyboardLayout(
+                                            descriptor, label, collection, priority,
+                                            locales, vid, pid);
+                                    visitor.visitKeyboardLayout(
+                                            resources, keyboardLayoutResId, layout);
+                                }
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    } else {
+                        Slog.w(TAG, "Skipping unrecognized element '" + element
+                                + "' in keyboard layout resource from receiver "
+                                + receiver.packageName + "/" + receiver.name);
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            Slog.w(TAG, "Could not parse keyboard layout resource from receiver "
+                    + receiver.packageName + "/" + receiver.name, ex);
+        }
+    }
+
+    @NonNull
+    private static LocaleList getLocalesFromLanguageTags(String languageTags) {
+        if (TextUtils.isEmpty(languageTags)) {
+            return LocaleList.getEmptyLocaleList();
+        }
+        return LocaleList.forLanguageTags(languageTags.replace('|', ','));
+    }
+
+    /**
+     * Builds a layout descriptor for the vendor/product. This returns the
+     * descriptor for ids that aren't useful (such as the default 0, 0).
+     */
+    private String getLayoutDescriptor(InputDeviceIdentifier identifier) {
+        Objects.requireNonNull(identifier, "identifier must not be null");
+        Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
+
+        if (identifier.getVendorId() == 0 && identifier.getProductId() == 0) {
+            return identifier.getDescriptor();
+        }
+        return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
+    }
+
+    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            String layout;
+            // try loading it using the layout descriptor if we have it
+            layout = mDataStore.getCurrentKeyboardLayout(key);
+            if (layout == null && !key.equals(identifier.getDescriptor())) {
+                // if it doesn't exist fall back to the device descriptor
+                layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
+                        + identifier.toString() + ": " + layout);
+            }
+            return layout;
+        }
+    }
+
+    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
+                                + " key: " + key
+                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
+                    }
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            String[] layouts = mDataStore.getKeyboardLayouts(key);
+            if ((layouts == null || layouts.length == 0)
+                    && !key.equals(identifier.getDescriptor())) {
+                layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
+            }
+            return layouts;
+        }
+    }
+
+    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
+                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
+                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
+                }
+                if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
+                        && !Objects.equals(oldLayout,
+                        mDataStore.getCurrentKeyboardLayout(key))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
+                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
+                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
+                }
+                boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
+                if (!key.equals(identifier.getDescriptor())) {
+                    // We need to remove from both places to ensure it is gone
+                    removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
+                            keyboardLayoutDescriptor);
+                }
+                if (removed && !Objects.equals(oldLayout,
+                        mDataStore.getCurrentKeyboardLayout(key))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public void switchKeyboardLayout(int deviceId, int direction) {
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+    }
+
+    // Must be called on handler.
+    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+        final InputDevice device = getInputDevice(deviceId);
+        if (device != null) {
+            final boolean changed;
+            final String keyboardLayoutDescriptor;
+
+            String key = getLayoutDescriptor(device.getIdentifier());
+            synchronized (mDataStore) {
+                try {
+                    changed = mDataStore.switchKeyboardLayout(key, direction);
+                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
+                            key);
+                } finally {
+                    mDataStore.saveIfNeeded();
+                }
+            }
+
+            if (changed) {
+                if (mSwitchedKeyboardLayoutToast != null) {
+                    mSwitchedKeyboardLayoutToast.cancel();
+                    mSwitchedKeyboardLayoutToast = null;
+                }
+                if (keyboardLayoutDescriptor != null) {
+                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
+                    if (keyboardLayout != null) {
+                        mSwitchedKeyboardLayoutToast = Toast.makeText(
+                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
+                        mSwitchedKeyboardLayoutToast.show();
+                    }
+                }
+
+                reloadKeyboardLayouts();
+            }
+        }
+    }
+
+    public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
+        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        if (keyboardLayoutDescriptor == null) {
+            return null;
+        }
+
+        final String[] result = new String[2];
+        visitKeyboardLayout(keyboardLayoutDescriptor,
+                (resources, keyboardLayoutResId, layout) -> {
+                    try (InputStreamReader stream = new InputStreamReader(
+                            resources.openRawResource(keyboardLayoutResId))) {
+                        result[0] = layout.getDescriptor();
+                        result[1] = Streams.readFully(stream);
+                    } catch (IOException | Resources.NotFoundException ignored) {
+                    }
+                });
+        if (result[0] == null) {
+            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+            return null;
+        }
+        return result;
+    }
+
+    private void reloadKeyboardLayouts() {
+        if (DEBUG) {
+            Slog.d(TAG, "Reloading keyboard layouts.");
+        }
+        mNative.reloadKeyboardLayouts();
+    }
+
+    private void maybeUpdateNotification() {
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        if (notificationManager == null) {
+            return;
+        }
+        if (!mKeyboardsWithMissingLayouts.isEmpty()) {
+            if (mKeyboardsWithMissingLayouts.size() > 1) {
+                // We have more than one keyboard missing a layout, so drop the
+                // user at the generic input methods page, so they can pick which
+                // one to set.
+                showMissingKeyboardLayoutNotification(notificationManager, null);
+            } else {
+                showMissingKeyboardLayoutNotification(notificationManager,
+                        mKeyboardsWithMissingLayouts.get(0));
+            }
+        } else if (mKeyboardLayoutNotificationShown) {
+            hideMissingKeyboardLayoutNotification(notificationManager);
+        }
+    }
+
+    // Must be called on handler.
+    private void showMissingKeyboardLayoutNotification(NotificationManager notificationManager,
+            InputDevice device) {
+        if (!mKeyboardLayoutNotificationShown) {
+            final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
+            if (device != null) {
+                intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
+            }
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
+                    intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+
+            Resources r = mContext.getResources();
+            Notification notification =
+                    new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
+                            .setContentTitle(r.getString(
+                                    R.string.select_keyboard_layout_notification_title))
+                            .setContentText(r.getString(
+                                    R.string.select_keyboard_layout_notification_message))
+                            .setContentIntent(keyboardLayoutIntent)
+                            .setSmallIcon(R.drawable.ic_settings_language)
+                            .setColor(mContext.getColor(
+                                    com.android.internal.R.color.system_notification_accent_color))
+                            .build();
+            notificationManager.notifyAsUser(null,
+                    SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+                    notification, UserHandle.ALL);
+            mKeyboardLayoutNotificationShown = true;
+        }
+    }
+
+    // Must be called on handler.
+    private void hideMissingKeyboardLayoutNotification(NotificationManager notificationManager) {
+        if (mKeyboardLayoutNotificationShown) {
+            mKeyboardLayoutNotificationShown = false;
+            notificationManager.cancelAsUser(null,
+                    SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+                    UserHandle.ALL);
+        }
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_SWITCH_KEYBOARD_LAYOUT:
+                handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                return true;
+            case MSG_RELOAD_KEYBOARD_LAYOUTS:
+                reloadKeyboardLayouts();
+                return true;
+            case MSG_UPDATE_KEYBOARD_LAYOUTS:
+                updateKeyboardLayouts();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private InputDevice getInputDevice(int deviceId) {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
+    }
+
+    private static final class KeyboardLayoutDescriptor {
+        public String packageName;
+        public String receiverName;
+        public String keyboardLayoutName;
+
+        public static String format(String packageName,
+                String receiverName, String keyboardName) {
+            return packageName + "/" + receiverName + "/" + keyboardName;
+        }
+
+        public static KeyboardLayoutDescriptor parse(String descriptor) {
+            int pos = descriptor.indexOf('/');
+            if (pos < 0 || pos + 1 == descriptor.length()) {
+                return null;
+            }
+            int pos2 = descriptor.indexOf('/', pos + 1);
+            if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
+                return null;
+            }
+
+            KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
+            result.packageName = descriptor.substring(0, pos);
+            result.receiverName = descriptor.substring(pos + 1, pos2);
+            result.keyboardLayoutName = descriptor.substring(pos2 + 1);
+            return result;
+        }
+    }
+
+    private interface KeyboardLayoutVisitor {
+        void visitKeyboardLayout(Resources resources,
+                int keyboardLayoutResId, KeyboardLayout layout);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 015e576..c53f1a5 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -110,11 +110,10 @@
 
     @AnyThread
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
-            int configChanges, @InputMethodNavButtonFlags int navigationBarFlags) {
+            @InputMethodNavButtonFlags int navigationBarFlags) {
         final IInputMethod.InitParams params = new IInputMethod.InitParams();
         params.token = token;
         params.privilegedOperations = privilegedOperations;
-        params.configChanges = configChanges;
         params.navigationBarFlags = navigationBarFlags;
         try {
             mTarget.initializeInternal(params);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 6dbb362..079234c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -42,12 +42,15 @@
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.server.EventLogTags;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.concurrent.CountDownLatch;
+
 /**
  * A controller managing the state of the input method binding.
  */
@@ -77,19 +80,26 @@
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
 
+    @Nullable private CountDownLatch mLatchForTesting;
+
     /**
      * Binding flags for establishing connection to the {@link InputMethodService}.
      */
-    private static final int IME_CONNECTION_BIND_FLAGS =
+    @VisibleForTesting
+    static final int IME_CONNECTION_BIND_FLAGS =
             Context.BIND_AUTO_CREATE
                     | Context.BIND_NOT_VISIBLE
                     | Context.BIND_NOT_FOREGROUND
                     | Context.BIND_IMPORTANT_BACKGROUND
                     | Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
+    private final int mImeConnectionBindFlags;
+
     /**
      * Binding flags used only while the {@link InputMethodService} is showing window.
      */
-    private static final int IME_VISIBLE_BIND_FLAGS =
+    @VisibleForTesting
+    static final int IME_VISIBLE_BIND_FLAGS =
             Context.BIND_AUTO_CREATE
                     | Context.BIND_TREAT_LIKE_ACTIVITY
                     | Context.BIND_FOREGROUND_SERVICE
@@ -97,12 +107,19 @@
                     | Context.BIND_SHOWING_UI;
 
     InputMethodBindingController(@NonNull InputMethodManagerService service) {
+        this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+    }
+
+    InputMethodBindingController(@NonNull InputMethodManagerService service,
+            int imeConnectionBindFlags, CountDownLatch latchForTesting) {
         mService = service;
         mContext = mService.mContext;
         mMethodMap = mService.mMethodMap;
         mSettings = mService.mSettings;
         mPackageManagerInternal = mService.mPackageManagerInternal;
         mWindowManagerInternal = mService.mWindowManagerInternal;
+        mImeConnectionBindFlags = imeConnectionBindFlags;
+        mLatchForTesting = latchForTesting;
     }
 
     /**
@@ -242,7 +259,7 @@
         @Override public void onBindingDied(ComponentName name) {
             synchronized (ImfLock.class) {
                 mService.invalidateAutofillSessionLocked();
-                if (mVisibleBound) {
+                if (isVisibleBound()) {
                     unbindVisibleConnection();
                 }
             }
@@ -279,7 +296,7 @@
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
                     mSupportsStylusHw = info.supportsStylusHandwriting();
-                    mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges());
+                    mService.initializeImeLocked(mCurMethod, mCurToken);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                     mService.performOnCreateInlineSuggestionsRequestLocked();
@@ -291,6 +308,10 @@
                 mService.scheduleResetStylusHandwriting();
             }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+            if (mLatchForTesting != null) {
+                mLatchForTesting.countDown(); // Notify the finish to tests
+            }
         }
 
         @GuardedBy("ImfLock.class")
@@ -338,15 +359,15 @@
 
     @GuardedBy("ImfLock.class")
     void unbindCurrentMethod() {
-        if (mVisibleBound) {
+        if (isVisibleBound()) {
             unbindVisibleConnection();
         }
 
-        if (mHasConnection) {
+        if (hasConnection()) {
             unbindMainConnection();
         }
 
-        if (mCurToken != null) {
+        if (getCurToken() != null) {
             removeCurrentToken();
             mService.resetSystemUiLocked();
         }
@@ -448,17 +469,17 @@
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
-        if (mCurIntent == null || conn == null) {
+        if (getCurIntent() == null || conn == null) {
             Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
             return false;
         }
-        return mContext.bindServiceAsUser(mCurIntent, conn, flags,
+        return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
                 new UserHandle(mSettings.getCurrentUserId()));
     }
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodServiceMainConnection() {
-        mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS);
+        mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
         return mHasConnection;
     }
 
@@ -472,7 +493,7 @@
     void setCurrentMethodVisible() {
         if (mCurMethod != null) {
             if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
-            if (mHasConnection && !mVisibleBound) {
+            if (hasConnection() && !isVisibleBound()) {
                 mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
                         IME_VISIBLE_BIND_FLAGS);
             }
@@ -480,7 +501,7 @@
         }
 
         // No IME is currently connected. Reestablish the main connection.
-        if (!mHasConnection) {
+        if (!hasConnection()) {
             if (DEBUG) {
                 Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
             }
@@ -512,7 +533,7 @@
      */
     @GuardedBy("ImfLock.class")
     void setCurrentMethodNotVisible() {
-        if (mVisibleBound) {
+        if (isVisibleBound()) {
             unbindVisibleConnection();
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b083bd..080d582 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2692,14 +2692,13 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
-            @android.content.pm.ActivityInfo.Config int configChanges) {
+    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
                     + mCurTokenDisplayId);
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
-                configChanges, getInputMethodNavButtonFlagsLocked());
+                getInputMethodNavButtonFlagsLocked());
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2669d21..3ce51c3 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -28,6 +28,7 @@
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
 import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER;
+import static android.location.provider.LocationProviderBase.ACTION_GNSS_PROVIDER;
 import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -140,9 +141,7 @@
 import com.android.server.location.provider.proxy.ProxyLocationProvider;
 import com.android.server.location.settings.LocationSettings;
 import com.android.server.location.settings.LocationUserSettings;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.utils.Slogf;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -310,10 +309,6 @@
         permissionManagerInternal.setLocationExtraPackagesProvider(
                 userId -> mContext.getResources().getStringArray(
                         com.android.internal.R.array.config_locationExtraPackageNames));
-
-        // TODO(b/241604546): properly handle this callback
-        LocalServices.getService(UserManagerInternal.class).addUserVisibilityListener(
-                (u, v) -> Slogf.i(TAG, "onUserVisibilityChanged(): %d -> %b", u, v));
     }
 
     @Nullable
@@ -445,9 +440,24 @@
             mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative);
             mGnssManagerService.onSystemReady();
 
+            boolean useGnssHardwareProvider = mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_useGnssHardwareProvider);
+            AbstractLocationProvider gnssProvider = null;
+            if (!useGnssHardwareProvider) {
+                gnssProvider = ProxyLocationProvider.create(
+                        mContext,
+                        GPS_PROVIDER,
+                        ACTION_GNSS_PROVIDER,
+                        com.android.internal.R.bool.config_useGnssHardwareProvider,
+                        com.android.internal.R.string.config_gnssLocationProviderPackageName);
+            }
+            if (gnssProvider == null) {
+                gnssProvider = mGnssManagerService.getGnssLocationProvider();
+            }
+
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
                     GPS_PROVIDER, mPassiveManager);
-            addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
+            addLocationProviderManager(gnssManager, gnssProvider);
         }
 
         // bind to geocoder provider
@@ -1702,7 +1712,7 @@
 
         private final Context mContext;
 
-        private final UserInfoHelper mUserInfoHelper;
+        private final SystemUserInfoHelper mUserInfoHelper;
         private final LocationSettings mLocationSettings;
         private final AlarmHelper mAlarmHelper;
         private final SystemAppOpsHelper mAppOpsHelper;
@@ -1725,7 +1735,7 @@
         @GuardedBy("this")
         private boolean mSystemReady;
 
-        SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+        SystemInjector(Context context, SystemUserInfoHelper userInfoHelper) {
             mContext = context;
 
             mUserInfoHelper = userInfoHelper;
@@ -1745,6 +1755,7 @@
         }
 
         synchronized void onSystemReady() {
+            mUserInfoHelper.onSystemReady();
             mAppOpsHelper.onSystemReady();
             mLocationPermissionsHelper.onSystemReady();
             mSettingsHelper.onSystemReady();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 45436e7..cb952ed 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -110,6 +110,11 @@
         addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
     }
 
+    /** Logs a user visibility changed event. */
+    public void logUserVisibilityChanged(int userId, boolean visible) {
+        addLog(new UserVisibilityChangedEvent(userId, visible));
+    }
+
     /** Logs a location enabled/disabled event. */
     public void logLocationEnabled(int userId, boolean enabled) {
         addLog(new LocationEnabledEvent(userId, enabled));
@@ -475,6 +480,22 @@
         }
     }
 
+    private static final class UserVisibilityChangedEvent {
+
+        private final int mUserId;
+        private final boolean mVisible;
+
+        UserVisibilityChangedEvent(int userId, boolean visible) {
+            mUserId = userId;
+            mVisible = visible;
+        }
+
+        @Override
+        public String toString() {
+            return "[u" + mUserId + "] " + (mVisible ? "visible" : "invisible");
+        }
+    }
+
     private static final class LocationEnabledEvent {
 
         private final int mUserId;
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 0f5e3d4..d3ceddd 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -387,7 +387,7 @@
             if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
                 return false;
             }
-            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+            if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
                 return false;
             }
             if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -534,7 +534,10 @@
     }
 
     void onUserChanged(int userId, int change) {
-        if (change == UserListener.CURRENT_USER_CHANGED) {
+        // current user changes affect whether system server location requests are allowed to access
+        // location, and visibility changes affect whether any given user may access location.
+        if (change == UserListener.CURRENT_USER_CHANGED
+                || change == UserListener.USER_VISIBILITY_CHANGED) {
             updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
         }
     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 349b94b..567d8ac 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -317,7 +317,7 @@
                     identity.getUserId())) {
                 return false;
             }
-            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+            if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
                 return false;
             }
             if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -394,7 +394,10 @@
     }
 
     private void onUserChanged(int userId, int change) {
-        if (change == UserListener.CURRENT_USER_CHANGED) {
+        // current user changes affect whether system server location requests are allowed to access
+        // location, and visibility changes affect whether any given user may access location.
+        if (change == UserListener.CURRENT_USER_CHANGED
+                || change == UserListener.USER_VISIBILITY_CHANGED) {
             updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
         }
     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f6b1c9..282ad57 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -81,6 +81,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
@@ -930,9 +931,15 @@
     }
 
     private void updateEnabled() {
-        // Generally follow location setting for current user
-        boolean enabled = mContext.getSystemService(LocationManager.class)
-                .isLocationEnabledForUser(UserHandle.CURRENT);
+        boolean enabled = false;
+
+        // Generally follow location setting for visible users
+        LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+        Set<UserHandle> visibleUserHandles =
+                mContext.getSystemService(UserManager.class).getVisibleUsers();
+        for (UserHandle visibleUserHandle : visibleUserHandles) {
+            enabled |= locationManager.isLocationEnabledForUser(visibleUserHandle);
+        }
 
         // .. but enable anyway, if there's an active bypass request (e.g. ELS or ADAS)
         enabled |= (mProviderRequest != null
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index ed1e654..40dd979 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -33,9 +33,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.FileDescriptor;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Provides accessors and listeners for all user info.
@@ -50,11 +52,21 @@
     @Nullable private IActivityManager mActivityManager;
     @GuardedBy("this")
     @Nullable private UserManager mUserManager;
+    @GuardedBy("this")
+    @Nullable private UserManagerInternal mUserManagerInternal;
 
     public SystemUserInfoHelper(Context context) {
         mContext = context;
     }
 
+    /** The function should be called when PHASE_SYSTEM_SERVICES_READY. */
+    public synchronized void onSystemReady() {
+        mUserManagerInternal =
+                Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
+        mUserManagerInternal.addUserVisibilityListener(
+                (userId, visible) -> dispatchOnVisibleUserChanged(userId, visible));
+    }
+
     @Nullable
     protected final ActivityManagerInternal getActivityManagerInternal() {
         synchronized (this) {
@@ -136,6 +148,24 @@
     }
 
     @Override
+    public boolean isVisibleUserId(@UserIdInt int userId) {
+        synchronized (this) {
+            // if you're hitting this precondition then you are invoking this before the system is
+            // ready
+            Preconditions.checkState(mUserManagerInternal != null);
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                return mUserManagerInternal.isUserVisible(userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     protected int[] getProfileIds(@UserIdInt int userId) {
         UserManager userManager = getUserManager();
 
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index c835370..2b9db1c 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -22,6 +22,7 @@
 import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
+import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_VISIBILITY_CHANGED;
 
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
@@ -47,8 +48,9 @@
         int CURRENT_USER_CHANGED = 1;
         int USER_STARTED = 2;
         int USER_STOPPED = 3;
+        int USER_VISIBILITY_CHANGED = 4;
 
-        @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED})
+        @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED, USER_VISIBILITY_CHANGED})
         @Retention(RetentionPolicy.SOURCE)
         @interface UserChange {}
 
@@ -121,6 +123,18 @@
         }
     }
 
+    protected final void dispatchOnVisibleUserChanged(@UserIdInt int userId, boolean visible) {
+        if (D) {
+            Log.d(TAG, "visibility of u" + userId + " changed to "
+                    + (visible ? "visible" : "invisible"));
+        }
+        EVENT_LOG.logUserVisibilityChanged(userId, visible);
+
+        for (UserListener listener : mListeners) {
+            listener.onUserChanged(userId, USER_VISIBILITY_CHANGED);
+        }
+    }
+
     /**
      * Returns an array of running user ids. This will include all running users, and will also
      * include any profiles of the running users. The caller must never mutate the returned
@@ -129,8 +143,8 @@
     public abstract int[] getRunningUserIds();
 
     /**
-     * Returns true if the given user id is either the current user or a profile of the current
-     * user.
+     * Returns {@code true} if the given user id is either the current user or a profile of the
+     * current user.
      */
     public abstract boolean isCurrentUserId(@UserIdInt int userId);
 
@@ -140,6 +154,13 @@
      */
     public abstract @UserIdInt int getCurrentUserId();
 
+    /**
+     * Returns {@code true} if the user is visible.
+     *
+     * <p>The visibility of a user is defined by {@link android.os.UserManager#isUserVisible()}.
+     */
+    public abstract boolean isVisibleUserId(@UserIdInt int userId);
+
     protected abstract int[] getProfileIds(@UserIdInt int userId);
 
     /**
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 338a995..7063cb8 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -661,6 +661,8 @@
                 if (!GPS_PROVIDER.equals(mName)) {
                     Log.e(TAG, "adas gnss bypass request received in non-gps provider");
                     adasGnssBypass = false;
+                } else if (!mUserHelper.isCurrentUserId(getIdentity().getUserId())) {
+                    adasGnssBypass = false;
                 } else if (!mLocationSettings.getUserSettings(
                         getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
                     adasGnssBypass = false;
@@ -1712,6 +1714,8 @@
             if (!GPS_PROVIDER.equals(mName)) {
                 Log.e(TAG, "adas gnss bypass request received in non-gps provider");
                 adasGnssBypass = false;
+            } else if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+                adasGnssBypass = false;
             } else if (!mLocationSettings.getUserSettings(
                     identity.getUserId()).isAdasGnssLocationEnabled()) {
                 adasGnssBypass = false;
@@ -2193,7 +2197,7 @@
                 if (!isEnabled(identity.getUserId())) {
                     return false;
                 }
-                if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+                if (!mUserHelper.isVisibleUserId(identity.getUserId())) {
                     return false;
                 }
             }
@@ -2322,6 +2326,10 @@
 
             switch (change) {
                 case UserListener.CURRENT_USER_CHANGED:
+                    // current user changes affect whether system server location requests are
+                    // allowed to access location, and visibility changes affect whether any given
+                    // user may access location.
+                case UserListener.USER_VISIBILITY_CHANGED:
                     updateRegistrations(
                             registration -> registration.getIdentity().getUserId() == userId);
                     break;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index a6b7fe2..d6846be 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -41,6 +41,7 @@
 import android.media.MediaRoute2ProviderService;
 import android.media.MediaRouter2Manager;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
@@ -257,6 +258,24 @@
         }
     }
 
+    public void setRouteListingPreference(
+            @NonNull IMediaRouter2 router,
+            @Nullable RouteListingPreference routeListingPreference) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
+                if (routerRecord == null) {
+                    Slog.w(TAG, "Ignoring updating route listing of null routerRecord.");
+                    return;
+                }
+                setRouteListingPreferenceLocked(routerRecord, routeListingPreference);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router,
             @NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(router, "router must not be null");
@@ -648,9 +667,11 @@
                                 "userId: %d", newActiveUserId));
 
                 mCurrentActiveUserId = newActiveUserId;
-                for (int i = 0; i < mUserRecords.size(); i++) {
-                    int userId = mUserRecords.keyAt(i);
-                    UserRecord userRecord = mUserRecords.valueAt(i);
+                // disposeUserIfNeededLocked might modify the collection, hence clone
+                final var userRecords = mUserRecords.clone();
+                for (int i = 0; i < userRecords.size(); i++) {
+                    int userId = userRecords.keyAt(i);
+                    UserRecord userRecord = userRecords.valueAt(i);
                     if (isUserActiveLocked(userId)) {
                         // userId corresponds to the active user, or one of its profiles. We
                         // ensure the associated structures are initialized.
@@ -771,6 +792,31 @@
                         routerRecord.mUserRecord.mHandler));
     }
 
+    @GuardedBy("mLock")
+    private void setRouteListingPreferenceLocked(
+            RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference) {
+        routerRecord.mRouteListingPreference = routeListingPreference;
+        String routeListingAsString =
+                routeListingPreference != null
+                        ? routeListingPreference.getItems().stream()
+                                .map(RouteListingPreference.Item::getRouteId)
+                                .collect(Collectors.joining(","))
+                        : null;
+        mEventLogger.enqueue(
+                EventLogger.StringEvent.from(
+                        "setRouteListingPreference",
+                        "router id: %d, route listing preference: [%s]",
+                        routerRecord.mRouterId,
+                        routeListingAsString));
+
+        routerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(
+                        UserHandler::notifyRouteListingPreferenceChangeToManagers,
+                        routerRecord.mUserRecord.mHandler,
+                        routerRecord.mPackageName,
+                        routeListingPreference));
+    }
+
     private void setRouteVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
             @NonNull MediaRoute2Info route, int volume) {
         final IBinder binder = router.asBinder();
@@ -1021,6 +1067,15 @@
         // RouteCallback#onRoutesAdded() for system MR2 will never be called with initial routes
         // due to the lack of features.
         for (RouterRecord routerRecord : userRecord.mRouterRecords) {
+            // Send route listing preferences before discovery preferences and routes to avoid an
+            // inconsistent state where there are routes to show, but the manager thinks
+            // the app has not expressed a preference for listing.
+            userRecord.mHandler.sendMessage(
+                    obtainMessage(
+                            UserHandler::notifyRouteListingPreferenceChangeToManagers,
+                            routerRecord.mUserRecord.mHandler,
+                            routerRecord.mPackageName,
+                            routerRecord.mRouteListingPreference));
             // TODO: UserRecord <-> routerRecord, why do they reference each other?
             // How about removing mUserRecord from routerRecord?
             routerRecord.mUserRecord.mHandler.sendMessage(
@@ -1400,6 +1455,7 @@
         public final int mRouterId;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
+        @Nullable public RouteListingPreference mRouteListingPreference;
 
         RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
                 String packageName, boolean hasConfigureWifiDisplayPermission,
@@ -1688,6 +1744,9 @@
                     indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
             MediaRoute2ProviderInfo oldInfo =
                     providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
+            MediaRouter2ServiceImpl mediaRouter2Service = mServiceRef.get();
+            EventLogger eventLogger =
+                    mediaRouter2Service != null ? mediaRouter2Service.mEventLogger : null;
             if (oldInfo == newInfo) {
                 // Nothing to do.
                 return;
@@ -1713,6 +1772,7 @@
             }
 
             // Add new routes to the maps.
+            ArrayList<MediaRoute2Info> addedRoutes = new ArrayList<>();
             boolean hasAddedOrModifiedRoutes = false;
             for (MediaRoute2Info newRouteInfo : newRoutes) {
                 if (!newRouteInfo.isValid()) {
@@ -1727,11 +1787,14 @@
                 MediaRoute2Info oldRouteInfo =
                         mLastNotifiedRoutesToPrivilegedRouters.put(
                                 newRouteInfo.getId(), newRouteInfo);
-                hasAddedOrModifiedRoutes |=
-                        oldRouteInfo == null || !oldRouteInfo.equals(newRouteInfo);
+                hasAddedOrModifiedRoutes |= !newRouteInfo.equals(oldRouteInfo);
+                if (oldRouteInfo == null) {
+                    addedRoutes.add(newRouteInfo);
+                }
             }
 
             // Remove stale routes from the maps.
+            ArrayList<MediaRoute2Info> removedRoutes = new ArrayList<>();
             Collection<MediaRoute2Info> oldRoutes =
                     oldInfo == null ? Collections.emptyList() : oldInfo.getRoutes();
             boolean hasRemovedRoutes = false;
@@ -1741,6 +1804,26 @@
                     hasRemovedRoutes = true;
                     mLastNotifiedRoutesToPrivilegedRouters.remove(oldRouteId);
                     mLastNotifiedRoutesToNonPrivilegedRouters.remove(oldRouteId);
+                    removedRoutes.add(oldRoute);
+                }
+            }
+
+            if (eventLogger != null) {
+                if (!addedRoutes.isEmpty()) {
+                    // If routes were added, newInfo cannot be null.
+                    eventLogger.enqueue(
+                            toLoggingEvent(
+                                    /* source= */ "addProviderRoutes",
+                                    newInfo.getUniqueId(),
+                                    addedRoutes));
+                }
+                if (!removedRoutes.isEmpty()) {
+                    // If routes were removed, oldInfo cannot be null.
+                    eventLogger.enqueue(
+                            toLoggingEvent(
+                                    /* source= */ "removeProviderRoutes",
+                                    oldInfo.getUniqueId(),
+                                    removedRoutes));
                 }
             }
 
@@ -1751,6 +1834,16 @@
                     mSystemProvider.getDefaultRoute());
         }
 
+        private static EventLogger.Event toLoggingEvent(
+                String source, String providerId, ArrayList<MediaRoute2Info> routes) {
+            String routesString =
+                    routes.stream()
+                            .map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
+                            .collect(Collectors.joining(/* delimiter= */ ", "));
+            return EventLogger.StringEvent.from(
+                    source, "provider: %s, routes: [%s]", providerId, routesString);
+        }
+
         /**
          * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
          * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
@@ -2427,6 +2520,34 @@
             }
         }
 
+        private void notifyRouteListingPreferenceChangeToManagers(
+                String routerPackageName, @Nullable RouteListingPreference routeListingPreference) {
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return;
+            }
+            List<IMediaRouter2Manager> managers = new ArrayList<>();
+            synchronized (service.mLock) {
+                for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
+                    managers.add(managerRecord.mManager);
+                }
+            }
+            for (IMediaRouter2Manager manager : managers) {
+                try {
+                    manager.notifyRouteListingPreferenceChange(
+                            routerPackageName, routeListingPreference);
+                } catch (RemoteException ex) {
+                    Slog.w(
+                            TAG,
+                            "Failed to notify preferred features changed."
+                                    + " Manager probably died.",
+                            ex);
+                }
+            }
+            // TODO(b/238178508): In order to support privileged media router instances, we also
+            //    need to update routers other than the one making the update.
+        }
+
         private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
                 int requestId, int reason) {
             try {
@@ -2506,7 +2627,6 @@
             }
             return null;
         }
-
     }
     static final class SessionCreationRequest {
         public final RouterRecord mRouterRecord;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index c0340b1..beab5ea 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -17,6 +17,7 @@
 package com.android.server.media;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.UserSwitchObserver;
@@ -43,6 +44,7 @@
 import android.media.RemoteDisplayState;
 import android.media.RemoteDisplayState.RemoteDisplayInfo;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
@@ -420,6 +422,14 @@
 
     // Binder call
     @Override
+    public void setRouteListingPreference(
+            @NonNull IMediaRouter2 router,
+            @Nullable RouteListingPreference routeListingPreference) {
+        mService2.setRouteListingPreference(router, routeListingPreference);
+    }
+
+    // Binder call
+    @Override
     public void setRouteVolumeWithRouter2(IMediaRouter2 router,
             MediaRoute2Info route, int volume) {
         mService2.setRouteVolumeWithRouter2(router, route, volume);
@@ -628,9 +638,11 @@
         synchronized (mLock) {
             if (mCurrentActiveUserId != newActiveUserId) {
                 mCurrentActiveUserId = newActiveUserId;
-                for (int i = 0; i < mUserRecords.size(); i++) {
-                    int userId = mUserRecords.keyAt(i);
-                    UserRecord userRecord = mUserRecords.valueAt(i);
+                // disposeUserIfNeededLocked might modify the collection, hence clone
+                final var userRecords = mUserRecords.clone();
+                for (int i = 0; i < userRecords.size(); i++) {
+                    int userId = userRecords.keyAt(i);
+                    UserRecord userRecord = userRecords.valueAt(i);
                     if (isUserActiveLocked(userId)) {
                         // userId corresponds to the active user, or one of its profiles. We
                         // ensure the associated structures are initialized.
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 337d5e5..f2a39b8 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -24,6 +24,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.server.utils.EventLogger;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -38,6 +40,8 @@
     private static final boolean DEBUG = MediaSessionService.DEBUG;
     private static final String TAG = "MediaSessionStack";
 
+    private static final int DUMP_EVENTS_MAX_COUNT = 70;
+
     /**
      * Listen the change in the media button session.
      */
@@ -57,6 +61,8 @@
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
+    private final EventLogger mEventLogger = new EventLogger(DUMP_EVENTS_MAX_COUNT, TAG);
+
     /**
      * The media button session which receives media key events.
      * It could be null if the previous media button session is released.
@@ -80,6 +86,11 @@
      * @param record The record to add.
      */
     public void addSession(MediaSessionRecordImpl record) {
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
+                "addSession() (to bottom of stack)",
+                "record: %s",
+                record
+        ));
         mSessions.add(record);
         clearCache(record.getUserId());
 
@@ -95,6 +106,11 @@
      * @param record The record to remove.
      */
     public void removeSession(MediaSessionRecordImpl record) {
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
+                "removeSession()",
+                "record: %s",
+                record
+        ));
         mSessions.remove(record);
         if (mMediaButtonSession == record) {
             // When the media button session is removed, nullify the media button session and do not
@@ -140,6 +156,11 @@
     public void onPlaybackStateChanged(
             MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
         if (shouldUpdatePriority) {
+            mEventLogger.enqueue(EventLogger.StringEvent.from(
+                    "onPlaybackStateChanged() - Pushing session to top",
+                    "record: %s",
+                    record
+            ));
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache(record.getUserId());
@@ -344,6 +365,8 @@
         for (MediaSessionRecordImpl record : mSessions) {
             record.dump(pw, indent);
         }
+        pw.println(prefix + "Session stack events:");
+        mEventLogger.dump(pw, indent);
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d6b9bd5..90135ad 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3797,13 +3797,13 @@
         }
 
         private void createNotificationChannelsImpl(String pkg, int uid,
-                ParceledListSlice channelsList, boolean fromTargetApp) {
-            createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
+                ParceledListSlice channelsList) {
+            createNotificationChannelsImpl(pkg, uid, channelsList,
                     ActivityTaskManager.INVALID_TASK_ID);
         }
 
         private void createNotificationChannelsImpl(String pkg, int uid,
-                ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
+                ParceledListSlice channelsList, int startingTaskId) {
             List<NotificationChannel> channels = channelsList.getList();
             final int channelsSize = channels.size();
             ParceledListSlice<NotificationChannel> oldChannels =
@@ -3815,7 +3815,7 @@
                 final NotificationChannel channel = channels.get(i);
                 Objects.requireNonNull(channel, "channel in list is null");
                 needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
-                        channel, fromTargetApp,
+                        channel, true /* fromTargetApp */,
                         mConditionProviders.isPackageOrComponentAllowed(
                                 pkg, UserHandle.getUserId(uid)));
                 if (needsPolicyFileChange) {
@@ -3851,7 +3851,6 @@
         @Override
         public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
             checkCallerIsSystemOrSameApp(pkg);
-            boolean fromTargetApp = !isCallerSystemOrPhone();  // if not system, it's from the app
             int taskId = ActivityTaskManager.INVALID_TASK_ID;
             try {
                 int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3860,15 +3859,14 @@
             } catch (RemoteException e) {
                 // Do nothing
             }
-            createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
-                    taskId);
+            createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
         }
 
         @Override
         public void createNotificationChannelsForPackage(String pkg, int uid,
                 ParceledListSlice channelsList) {
             enforceSystemOrSystemUI("only system can call this");
-            createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
+            createNotificationChannelsImpl(pkg, uid, channelsList);
         }
 
         @Override
@@ -3883,8 +3881,7 @@
                     CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
             conversationChannel.setConversationId(parentId, conversationId);
             createNotificationChannelsImpl(
-                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
-                    false /* fromTargetApp */);
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
             mRankingHandler.requestSort();
             handleSavePolicyFile();
         }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 444fef6..1bbcc83 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -918,7 +918,7 @@
                 throw new IllegalArgumentException("Reserved id");
             }
             NotificationChannel existing = r.channels.get(channel.getId());
-            if (existing != null) {
+            if (existing != null && fromTargetApp) {
                 // Actually modifying an existing channel - keep most of the existing settings
                 if (existing.isDeleted()) {
                     // The existing channel was deleted - undelete it.
@@ -1004,7 +1004,9 @@
                 }
                 if (fromTargetApp) {
                     channel.setLockscreenVisibility(r.visibility);
-                    channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+                    channel.setAllowBubbles(existing != null
+                            ? existing.getAllowBubbles()
+                            : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
                 }
                 clearLockedFieldsLocked(channel);
 
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index f3cfa95..b8bdabe 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -213,7 +213,7 @@
 
         final int appId = UserHandle.getAppId(pkg.getUid());
 
-        String pkgSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+        String pkgSeInfo = ps.getSeInfo();
 
         Preconditions.checkNotNull(pkgSeInfo);
 
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
new file mode 100644
index 0000000..9ea350f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -0,0 +1,172 @@
+/*
+ * 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;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.media.IAudioService;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class to provide queries for app states concerning gentle-update.
+ */
+public class AppStateHelper {
+    private final Context mContext;
+
+    public AppStateHelper(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * True if the package is loaded into the process.
+     */
+    private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) {
+        return ArrayUtils.contains(info.pkgList, packageName)
+                || ArrayUtils.contains(info.pkgDeps, packageName);
+    }
+
+    /**
+     * Returns the importance of the given package.
+     */
+    private int getImportance(String packageName) {
+        var am = mContext.getSystemService(ActivityManager.class);
+        return am.getPackageImportance(packageName);
+    }
+
+    /**
+     * True if the app owns the audio focus.
+     */
+    private boolean hasAudioFocus(String packageName) {
+        var audioService = IAudioService.Stub.asInterface(
+                ServiceManager.getService(Context.AUDIO_SERVICE));
+        try {
+            var focusInfos = audioService.getFocusStack();
+            int size = focusInfos.size();
+            var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null;
+            return TextUtils.equals(packageName, audioFocusPackage);
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
+     * True if the app is in the foreground.
+     */
+    private boolean isAppForeground(String packageName) {
+        return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+    }
+
+    /**
+     * True if the app is currently at the top of the screen that the user is interacting with.
+     */
+    public boolean isAppTopVisible(String packageName) {
+        return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+    }
+
+    /**
+     * True if the app is playing/recording audio.
+     */
+    private boolean hasActiveAudio(String packageName) {
+        // TODO(b/235306967): also check recording
+        return hasAudioFocus(packageName);
+    }
+
+    /**
+     * True if the app is sending or receiving network data.
+     */
+    private boolean hasActiveNetwork(String packageName) {
+        // To be implemented
+        return false;
+    }
+
+    /**
+     * True if any app is interacting with the user.
+     */
+    public boolean hasInteractingApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (hasActiveAudio(packageName)
+                    || hasActiveNetwork(packageName)
+                    || isAppTopVisible(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is in the foreground.
+     */
+    public boolean hasForegroundApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (isAppForeground(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is top visible.
+     */
+    public boolean hasTopVisibleApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (isAppTopVisible(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if there is an ongoing phone call.
+     */
+    public boolean isInCall() {
+        // To be implemented
+        return false;
+    }
+
+    /**
+     * Returns a list of packages which depend on {@code packageNames}. These are the packages
+     * that will be affected when updating {@code packageNames} and should participate in
+     * the evaluation of install constraints.
+     *
+     * TODO(b/235306967): Also include bounded services as dependency.
+     */
+    public List<String> getDependencyPackages(List<String> packageNames) {
+        var results = new ArraySet<String>();
+        var am = mContext.getSystemService(ActivityManager.class);
+        for (var info : am.getRunningAppProcesses()) {
+            for (var packageName : packageNames) {
+                if (!isPackageLoaded(info, packageName)) {
+                    continue;
+                }
+                for (var pkg : info.pkgList) {
+                    results.add(pkg);
+                }
+            }
+        }
+        return new ArrayList<>(results);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index dd41830..cda7503 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -92,6 +92,8 @@
             new ComponentName("android", BackgroundDexOptJobService.class.getName());
 
     // Possible return codes of individual optimization steps.
+    /** Initial value. */
+    public static final int STATUS_UNSPECIFIED = -1;
     /** Ok status: Optimizations finished, All packages were processed, can continue */
     public static final int STATUS_OK = 0;
     /** Optimizations should be aborted. Job scheduler requested it. */
@@ -108,16 +110,20 @@
      * job will exclude those failed packages.
      */
     public static final int STATUS_DEX_OPT_FAILED = 5;
+    /** Encountered fatal error, such as a runtime exception. */
+    public static final int STATUS_FATAL_ERROR = 6;
 
     @IntDef(prefix = {"STATUS_"},
             value =
                     {
+                            STATUS_UNSPECIFIED,
                             STATUS_OK,
                             STATUS_ABORT_BY_CANCELLATION,
                             STATUS_ABORT_NO_SPACE_LEFT,
                             STATUS_ABORT_THERMAL,
                             STATUS_ABORT_BATTERY,
                             STATUS_DEX_OPT_FAILED,
+                            STATUS_FATAL_ERROR,
                     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Status {}
@@ -153,7 +159,7 @@
     // True if JobScheduler invocations of dexopt have been disabled.
     @GuardedBy("mLock") private boolean mDisableJobSchedulerJobs;
 
-    @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_OK;
+    @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_UNSPECIFIED;
 
     @GuardedBy("mLock") private long mLastExecutionStartUptimeMs;
     @GuardedBy("mLock") private long mLastExecutionDurationMs;
@@ -561,18 +567,26 @@
     private boolean runIdleOptimization(
             PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate) {
         synchronized (mLock) {
+            mLastExecutionStatus = STATUS_UNSPECIFIED;
             mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
             mLastExecutionDurationMs = -1;
         }
-        long lowStorageThreshold = getLowStorageThreshold();
-        int status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
-        logStatus(status);
-        synchronized (mLock) {
-            mLastExecutionStatus = status;
-            mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
-        }
 
-        return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
+        int status = STATUS_UNSPECIFIED;
+        try {
+            long lowStorageThreshold = getLowStorageThreshold();
+            status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
+            logStatus(status);
+            return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
+        } catch (RuntimeException e) {
+            status = STATUS_FATAL_ERROR;
+            throw e;
+        } finally {
+            synchronized (mLock) {
+                mLastExecutionStatus = status;
+                mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
+            }
+        }
     }
 
     /** Gets the size of the directory. It uses recursion to go over all files. */
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
new file mode 100644
index 0000000..247ac90
--- /dev/null
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -0,0 +1,171 @@
+/*
+ * 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;
+
+import android.annotation.WorkerThread;
+import android.app.ActivityThread;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Slog;
+
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper class to coordinate install flow for sessions with install constraints.
+ * These sessions will be pending and wait until the constraints are satisfied to
+ * resume installation.
+ */
+public class GentleUpdateHelper {
+    private static final String TAG = "GentleUpdateHelper";
+    private static final int JOB_ID = 235306967; // bug id
+    // The timeout used to determine whether the device is idle or not.
+    private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
+    /**
+     * A wrapper class used by JobScheduler to schedule jobs.
+     */
+    public static class Service extends JobService {
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            try {
+                var pis = (PackageInstallerService) ActivityThread.getPackageManager()
+                        .getPackageInstaller();
+                var helper = pis.getGentleUpdateHelper();
+                helper.mHandler.post(helper::runIdleJob);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to get PackageInstallerService", e);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+    }
+
+    private static class PendingInstallConstraintsCheck {
+        public final List<String> packageNames;
+        public final InstallConstraints constraints;
+        public final CompletableFuture<InstallConstraintsResult> future;
+        PendingInstallConstraintsCheck(List<String> packageNames,
+                InstallConstraints constraints,
+                CompletableFuture<InstallConstraintsResult> future) {
+            this.packageNames = packageNames;
+            this.constraints = constraints;
+            this.future = future;
+        }
+    }
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final AppStateHelper mAppStateHelper;
+    // Worker thread only
+    private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
+    private boolean mHasPendingIdleJob;
+
+    GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
+        mContext = context;
+        mHandler = new Handler(looper);
+        mAppStateHelper = appStateHelper;
+    }
+
+    /**
+     * Checks if install constraints are satisfied for the given packages.
+     */
+    CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
+            List<String> packageNames, InstallConstraints constraints) {
+        var future = new CompletableFuture<InstallConstraintsResult>();
+        mHandler.post(() -> {
+            var pendingCheck = new PendingInstallConstraintsCheck(
+                    packageNames, constraints, future);
+            if (constraints.isRequireDeviceIdle()) {
+                mPendingChecks.add(pendingCheck);
+                // JobScheduler doesn't provide queries about whether the device is idle.
+                // We schedule 2 tasks to determine device idle. If the idle job is executed
+                // before the delayed runnable, we know the device is idle.
+                // Note #processPendingCheck will be no-op for the task executed later.
+                scheduleIdleJob();
+                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+                        PENDING_CHECK_MILLIS);
+            } else {
+                processPendingCheck(pendingCheck, false);
+            }
+        });
+        return future;
+    }
+
+    @WorkerThread
+    private void scheduleIdleJob() {
+        if (mHasPendingIdleJob) {
+            // No need to schedule the job again
+            return;
+        }
+        mHasPendingIdleJob = true;
+        var componentName = new ComponentName(
+                mContext.getPackageName(), GentleUpdateHelper.Service.class.getName());
+        var jobInfo = new JobInfo.Builder(JOB_ID, componentName)
+                .setRequiresDeviceIdle(true)
+                .build();
+        var jobScheduler = mContext.getSystemService(JobScheduler.class);
+        jobScheduler.schedule(jobInfo);
+    }
+
+    @WorkerThread
+    private void runIdleJob() {
+        mHasPendingIdleJob = false;
+        processPendingChecksInIdle();
+    }
+
+    @WorkerThread
+    private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+        var future = pendingCheck.future;
+        if (future.isDone()) {
+            return;
+        }
+        var constraints = pendingCheck.constraints;
+        var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+        var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
+                && (!constraints.isRequireAppNotForeground()
+                        || !mAppStateHelper.hasForegroundApp(packageNames))
+                && (!constraints.isRequireAppNotInteracting()
+                        || !mAppStateHelper.hasInteractingApp(packageNames))
+                && (!constraints.isRequireAppNotTopVisible()
+                        || !mAppStateHelper.hasTopVisibleApp(packageNames))
+                && (!constraints.isRequireNotInCall()
+                        || !mAppStateHelper.isInCall());
+        future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+    }
+
+    @WorkerThread
+    private void processPendingChecksInIdle() {
+        while (!mPendingChecks.isEmpty()) {
+            processPendingCheck(mPendingChecks.remove(), true);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index a94a4e2..ced547c 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -59,6 +59,7 @@
     final boolean mForceQueryableOverride;
     final int mDataLoaderType;
     final int mPackageSource;
+    final boolean mKeepApplicationEnabledSetting;
 
     // The list of instruction sets supported by this app. This is currently
     // only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -72,7 +73,8 @@
             List<String> allowlistedRestrictedPermissions,
             int autoRevokePermissionsMode, String traceMethod, int traceCookie,
             SigningDetails signingDetails, int installReason, int installScenario,
-            boolean forceQueryableOverride, int dataLoaderType, int packageSource) {
+            boolean forceQueryableOverride, int dataLoaderType, int packageSource,
+            boolean keepApplicationEnabledSetting) {
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mInstallFlags = installFlags;
@@ -93,6 +95,7 @@
         mForceQueryableOverride = forceQueryableOverride;
         mDataLoaderType = dataLoaderType;
         mPackageSource = packageSource;
+        mKeepApplicationEnabledSetting = keepApplicationEnabledSetting;
     }
 
     /**
@@ -104,7 +107,7 @@
                 null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
                 SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
                 PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
-                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false);
         mCodeFile = (codePath != null) ? new File(codePath) : null;
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b02d1a8..283640d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DEPRECATED_SDK_VERSION;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP;
@@ -136,6 +137,7 @@
 import android.os.incremental.IncrementalStorage;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.provider.DeviceConfig;
 import android.stats.storage.StorageEnums;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -763,7 +765,7 @@
                 || (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
 
         if (ps != null && doSnapshotOrRestore) {
-            final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps);
+            final String seInfo = ps.getSeInfo();
             final RollbackManagerInternal rollbackManager =
                     mInjector.getLocalService(RollbackManagerInternal.class);
             rollbackManager.snapshotAndRestoreUserData(packageName,
@@ -1017,6 +1019,28 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
+        // If the minimum installable SDK version enforcement is enabled, block the install
+        // of apps using a lower target SDK version than required. This helps improve security
+        // and privacy as malware can target older SDK versions to avoid enforcement of new API
+        // behavior.
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                "MinInstallableTargetSdk__install_block_enabled",
+                false)) {
+            int minInstallableTargetSdk =
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                            "MinInstallableTargetSdk__min_installable_target_sdk",
+                            0);
+            if (parsedPackage.getTargetSdkVersion() < minInstallableTargetSdk) {
+                Slog.w(TAG, "App " + parsedPackage.getPackageName()
+                        + " targets deprecated sdk version");
+                throw new PrepareFailure(INSTALL_FAILED_DEPRECATED_SDK_VERSION,
+                        "App package must target at least version "
+                                + minInstallableTargetSdk);
+            }
+        } else {
+            Slog.i(TAG, "Minimum installable target sdk enforcement not enabled");
+        }
+
         // Instant apps have several additional install-time checks.
         if (instantApp) {
             if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
@@ -2020,7 +2044,8 @@
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
                     // Enable system package for requested users
-                    if (installedForUsers != null) {
+                    if (installedForUsers != null
+                            && !installRequest.isKeepApplicationEnabledSetting()) {
                         for (int origUserId : installedForUsers) {
                             if (userId == UserHandle.USER_ALL || userId == origUserId) {
                                 ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
@@ -2070,16 +2095,22 @@
 
                 if (userId != UserHandle.USER_ALL) {
                     // It's implied that when a user requests installation, they want the app to
-                    // be installed and enabled.
+                    // be installed and enabled. The caller, however, can explicitly specify to
+                    // keep the existing enabled state.
                     ps.setInstalled(true, userId);
-                    ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+                    if (!installRequest.isKeepApplicationEnabledSetting()) {
+                        ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
+                                installerPackageName);
+                    }
                 } else if (allUsers != null) {
                     // The caller explicitly specified INSTALL_ALL_USERS flag.
                     // Thus, updating the settings to install the app for all users.
                     for (int currentUserId : allUsers) {
                         ps.setInstalled(true, currentUserId);
-                        ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
-                                installerPackageName);
+                        if (!installRequest.isKeepApplicationEnabledSetting()) {
+                            ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
+                                    installerPackageName);
+                        }
                     }
                 }
 
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 71571dc..5974a9c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -128,7 +128,8 @@
                 params.mAutoRevokePermissionsMode,
                 params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
                 params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
-                params.mDataLoaderType, params.mPackageSource);
+                params.mDataLoaderType, params.mPackageSource,
+                params.mKeepApplicationEnabledSetting);
         mPackageMetrics = new PackageMetrics(this);
         mIsInstallInherit = params.mIsInherit;
         mSessionId = params.mSessionId;
@@ -498,6 +499,10 @@
         return mScanResult.mChangedAbiCodePath;
     }
 
+    public boolean isKeepApplicationEnabledSetting() {
+        return mInstallArgs == null ? false : mInstallArgs.mKeepApplicationEnabledSetting;
+    }
+
     public boolean isForceQueryableOverride() {
         return mInstallArgs != null && mInstallArgs.mForceQueryableOverride;
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 69ced1b..2b6398a 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -98,6 +98,7 @@
     final boolean mIsInherit;
     final int mSessionId;
     final int mRequireUserAction;
+    final boolean mKeepApplicationEnabledSetting;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,6 +131,7 @@
         mIsInherit = false;
         mSessionId = -1;
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
+        mKeepApplicationEnabledSetting = false;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
@@ -163,6 +165,7 @@
         mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
         mSessionId = sessionId;
         mRequireUserAction = sessionParams.requireUserAction;
+        mKeepApplicationEnabledSetting = sessionParams.keepApplicationEnabledSetting;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index b27373e..b66c6ac 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -129,7 +129,7 @@
         final InstallSource installSource = packageState.getInstallSource();
         final String packageAbiOverride = packageState.getCpuAbiOverride();
         final int appId = UserHandle.getAppId(pkg.getUid());
-        final String seinfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+        final String seinfo = packageState.getSeInfo();
         final String label = String.valueOf(pm.getApplicationLabel(
                 AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
         final int targetSdkVersion = pkg.getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 226a27e..49f3a3c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -493,7 +493,7 @@
             // TODO: Consider adding 2 different APIs for primary and secondary dexopt.
             // installd only uses downgrade flag for secondary dex files and ignores it for
             // primary dex files.
-            String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+            String seInfo = pkgSetting.getSeInfo();
             boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
                     dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
                     classLoaderContext, seInfo, /* downgrade= */ false ,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 653a882..409d352 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -44,6 +44,7 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallConstraints;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageItemInfo;
@@ -54,12 +55,14 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SELinux;
@@ -88,6 +91,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.IoThread;
@@ -186,6 +190,7 @@
 
     private final InternalCallback mInternalCallback = new InternalCallback();
     private final PackageSessionVerifier mSessionVerifier;
+    private final GentleUpdateHelper mGentleUpdateHelper;
 
     /**
      * Used for generating session IDs. Since this is created at boot time,
@@ -272,6 +277,8 @@
         mStagingManager = new StagingManager(context);
         mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
                 apexParserSupplier, mInstallThread.getLooper());
+        mGentleUpdateHelper = new GentleUpdateHelper(
+                context, mInstallThread.getLooper(), new AppStateHelper(context));
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -1233,6 +1240,33 @@
     }
 
     @Override
+    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, RemoteCallback callback) {
+        Preconditions.checkArgument(packageNames != null);
+        Preconditions.checkArgument(constraints != null);
+        Preconditions.checkArgument(callback != null);
+
+        final var snapshot = mPm.snapshotComputer();
+        final int callingUid = Binder.getCallingUid();
+        if (!isCalledBySystemOrShell(callingUid)) {
+            for (var packageName : packageNames) {
+                var ps = snapshot.getPackageStateInternal(packageName);
+                if (ps == null || !TextUtils.equals(
+                        ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
+                    throw new SecurityException("Caller has no access to package " + packageName);
+                }
+            }
+        }
+
+        var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+        future.thenAccept(result -> {
+            var b = new Bundle();
+            b.putParcelable("result", result);
+            callback.sendResult(b);
+        });
+    }
+
+    @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -1265,6 +1299,11 @@
     }
 
     @Override
+    public GentleUpdateHelper getGentleUpdateHelper() {
+        return mGentleUpdateHelper;
+    }
+
+    @Override
     public void bypassNextStagedInstallerCheck(boolean value) {
         if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
             throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2ee12bf..3983acf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -269,6 +269,8 @@
     private static final String ATTR_SIGNATURE = "signature";
     private static final String ATTR_CHECKSUM_KIND = "checksumKind";
     private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
+    private static final String ATTR_KEEP_APPLICATION_ENABLED_SETTING =
+            "keepApplicationEnabledSetting";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -1098,6 +1100,7 @@
             info.requireUserAction = params.requireUserAction;
             info.installerUid = mInstallerUid;
             info.packageSource = params.packageSource;
+            info.keepApplicationEnabledSetting = params.keepApplicationEnabledSetting;
         }
         return info;
     }
@@ -4310,6 +4313,11 @@
         mPreapprovalRequested.set(true);
     }
 
+    @Override
+    public boolean isKeepApplicationEnabledSetting() {
+        return params.keepApplicationEnabledSetting;
+    }
+
     void setSessionReady() {
         synchronized (mLock) {
             // Do not allow destroyed/failed session to change state
@@ -4691,6 +4699,8 @@
             writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
             writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
             out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason);
+            writeBooleanAttribute(out, ATTR_KEEP_APPLICATION_ENABLED_SETTING,
+                    params.keepApplicationEnabledSetting);
 
             final boolean isDataLoader = params.dataLoaderParams != null;
             writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
@@ -4852,6 +4862,8 @@
         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
         params.installReason = in.getAttributeInt(null, ATTR_INSTALL_REASON);
         params.packageSource = in.getAttributeInt(null, ATTR_PACKAGE_SOURCE);
+        params.keepApplicationEnabledSetting = in.getAttributeBoolean(null,
+                ATTR_KEEP_APPLICATION_ENABLED_SETTING, false);
 
         if (in.getAttributeBoolean(null, ATTR_IS_DATALOADER, false)) {
             params.dataLoaderParams = new DataLoaderParams(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f8cc8a..9e1bffb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1560,7 +1560,7 @@
                 AndroidPackage pkg = packageState.getPkg();
                 SharedUserApi sharedUser = snapshot.getSharedUser(
                         packageState.getSharedUserAppId());
-                String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+                String oldSeInfo = packageState.getSeInfo();
 
                 if (pkg == null) {
                     Slog.e(TAG, "Failed to find package " + packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index cc1306d..e1efc61 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3234,6 +3234,9 @@
                 case "--skip-verification":
                     sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
                     break;
+                case "--skip-enable":
+                    sessionParams.setKeepApplicationEnabledSetting();
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 3dcf926..81f1a98 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.os.Process.INVALID_UID;
+
 import android.annotation.IntDef;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -209,4 +211,35 @@
                 deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
                 !info.mRemovedForAllUsers);
     }
+
+    public static void onVerificationFailed(VerifyingSession verifyingSession) {
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+                verifyingSession.getSessionId() /* session_id */,
+                null /* package_name */,
+                INVALID_UID /* uid */,
+                null /* user_ids */,
+                null /* user_types */,
+                null /* original_user_ids */,
+                null /* original_user_types */,
+                verifyingSession.getRet() /* public_return_code */,
+                0 /* internal_error_code */,
+                0 /* apks_size_bytes */,
+                0 /* version_code */,
+                null /* install_steps */,
+                null /* step_duration_millis */,
+                0 /* total_duration_millis */,
+                0 /* install_flags */,
+                verifyingSession.getInstallerPackageUid() /* installer_package_uid */,
+                INVALID_UID /* original_installer_package_uid */,
+                verifyingSession.getDataLoaderType() /* data_loader_type */,
+                verifyingSession.getUserActionRequiredType() /* user_action_required_type */,
+                verifyingSession.isInstant() /* is_instant */,
+                false /* is_replace */,
+                false /* is_system */,
+                verifyingSession.isInherit() /* is_inherit */,
+                false /* is_installing_existing_as_user */,
+                false /* is_move_install */,
+                verifyingSession.isStaged() /* is_staged */
+        );
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index ad5cf13..79b88b3 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -29,4 +29,9 @@
     PackageInstallerSession getSession(int sessionId);
 
     PackageSessionVerifier getSessionVerifier();
+
+    /**
+     * Get the GentleUpdateHelper instance.
+     */
+    GentleUpdateHelper getGentleUpdateHelper();
 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6d90593..3ec6e7d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1358,6 +1358,17 @@
     }
 
     @Nullable
+    @Override
+    public String getSeInfo() {
+        String overrideSeInfo = getTransientState().getOverrideSeInfo();
+        if (!TextUtils.isEmpty(overrideSeInfo)) {
+            return overrideSeInfo;
+        }
+
+        return getTransientState().getSeInfo();
+    }
+
+    @Nullable
     public String getPrimaryCpuAbiLegacy() {
         return mPrimaryCpuAbi;
     }
@@ -1518,10 +1529,10 @@
     }
 
     @DataClass.Generated(
-            time = 1662666062860L,
+            time = 1665779003744L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index a905df9..6572d7b 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -265,8 +265,8 @@
             pkgSetting.getPkgState().setUpdatedSystemApp(true);
         }
 
-        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
-                injector.getCompatibility()));
+        pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage,
+                sharedUserSetting, injector.getCompatibility()));
 
         if (parsedPackage.isSystem()) {
             configurePackageComponents(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a40d404..4aba016 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,7 +102,6 @@
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -2900,7 +2899,7 @@
                 sb.append(isDebug ? " 1 " : " 0 ");
                 sb.append(dataPath);
                 sb.append(" ");
-                sb.append(AndroidPackageUtils.getSeInfo(pkg.getPkg(), pkg));
+                sb.append(pkg.getSeInfo());
                 sb.append(" ");
                 final int gidsSize = gids.size();
                 if (gids != null && gids.size() > 0) {
@@ -4359,7 +4358,7 @@
                     // (CE storage is not ready yet; the CE data directories will be created later,
                     // when the user is "unlocked".)  Accumulate all required args, and call the
                     // installer after the mPackages lock has been released.
-                    final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
+                    final String seInfo = ps.getSeInfo();
                     final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
                     final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
                             ps.getVolumeUuid(), ps.getPackageName(), userHandle,
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 1da442b..74594cc 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
@@ -347,7 +346,7 @@
             // an update, and hence need to restore data for all installed users.
             final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true);
 
-            final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+            final String seInfo = ps.getSeInfo();
             rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
                     appId, ceDataInode, seInfo, 0 /*token*/);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 1027f4c..df132a9 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -441,4 +441,12 @@
     /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
      */
     public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
+
+    /**
+     * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is
+     * no main user.
+     *
+     * @see UserManager#isMainUser()
+     */
+    public abstract @UserIdInt int getMainUserId();
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 88e12fa..b5c1206 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -901,6 +901,25 @@
         return null;
     }
 
+    @Override
+    public @UserIdInt int getMainUserId() {
+        checkQueryOrCreateUsersPermission("get main user id");
+        return getMainUserIdUnchecked();
+    }
+
+    private @UserIdInt int getMainUserIdUnchecked() {
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserInfo user = mUsers.valueAt(i).info;
+                if (user.isMain() && !mRemovingUserIds.get(user.id)) {
+                    return user.id;
+                }
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
@@ -3339,13 +3358,13 @@
                     Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): no system user data");
                     return;
                 }
+                final int oldMainUserId = getMainUserIdUnchecked();
                 final int oldFlags = systemUserData.info.flags;
                 final int newFlags;
                 final String newUserType;
-                // TODO(b/256624031): Also handle FLAG_MAIN
                 if (newHeadlessSystemUserMode) {
                     newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
-                    newFlags = oldFlags & ~UserInfo.FLAG_FULL;
+                    newFlags = oldFlags & ~UserInfo.FLAG_FULL & ~UserInfo.FLAG_MAIN;
                 } else {
                     newUserType = UserManager.USER_TYPE_FULL_SYSTEM;
                     newFlags = oldFlags | UserInfo.FLAG_FULL;
@@ -3360,9 +3379,38 @@
                         + "%s, flags changed from %s to %s",
                         systemUserData.info.userType, newUserType,
                         UserInfo.flagsToString(oldFlags), UserInfo.flagsToString(newFlags));
+
                 systemUserData.info.userType = newUserType;
                 systemUserData.info.flags = newFlags;
                 writeUserLP(systemUserData);
+
+                // Switch the MainUser to a reasonable choice if needed.
+                // (But if there was no MainUser, we deliberately continue to have no MainUser.)
+                final UserData oldMain = getUserDataNoChecks(oldMainUserId);
+                if (newHeadlessSystemUserMode) {
+                    if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) != 0) {
+                        // System was MainUser. So we need a new choice for Main. Pick the oldest.
+                        // If no oldest, don't set any. Let the BootUserInitializer do that later.
+                        final UserInfo newMainUser = getEarliestCreatedFullUser();
+                        if (newMainUser != null) {
+                            Slogf.i(LOG_TAG, "Designating user " + newMainUser.id + " to be Main");
+                            newMainUser.flags |= UserInfo.FLAG_MAIN;
+                            writeUserLP(getUserDataNoChecks(newMainUser.id));
+                        }
+                    }
+                } else {
+                    // TODO(b/256624031): For now, we demand the Main user (if there is one) is
+                    //  always the system in non-HSUM. In the future, when we relax this, change how
+                    //  we handle MAIN.
+                    if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) == 0) {
+                        // Someone else was the MainUser; transfer it to System.
+                        Slogf.i(LOG_TAG, "Transferring Main to user 0 from " + oldMain.info.id);
+                        oldMain.info.flags &= ~UserInfo.FLAG_MAIN;
+                        systemUserData.info.flags |= UserInfo.FLAG_MAIN;
+                        writeUserLP(oldMain);
+                        writeUserLP(systemUserData);
+                    }
+                }
             }
         }
 
@@ -3639,8 +3687,10 @@
             // Add FLAG_MAIN
             if (isHeadlessSystemUserMode()) {
                 final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
-                earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
-                userIdsToWrite.add(earliestCreatedUser.id);
+                if (earliestCreatedUser != null) {
+                    earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+                    userIdsToWrite.add(earliestCreatedUser.id);
+                }
             } else {
                 synchronized (mUsersLock) {
                     final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
@@ -3780,13 +3830,14 @@
         userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
     }
 
-    private UserInfo getEarliestCreatedFullUser() {
+    /** Returns the oldest Full Admin user, or null is if there none. */
+    private @Nullable UserInfo getEarliestCreatedFullUser() {
         final List<UserInfo> users = getUsersInternal(true, true, true);
-        UserInfo earliestUser = users.get(0);
-        long earliestCreationTime = earliestUser.creationTime;
+        UserInfo earliestUser = null;
+        long earliestCreationTime = Long.MAX_VALUE;
         for (int i = 0; i < users.size(); i++) {
             final UserInfo info = users.get(i);
-            if (info.isFull() && info.isAdmin() && info.creationTime > 0
+            if (info.isFull() && info.isAdmin() && info.creationTime >= 0
                     && info.creationTime < earliestCreationTime) {
                 earliestCreationTime = info.creationTime;
                 earliestUser = info;
@@ -6898,6 +6949,12 @@
             }
             return userTypes;
         }
+
+        @Override
+        public @UserIdInt int getMainUserId() {
+            return getMainUserIdUnchecked();
+        }
+
     } // class LocalService
 
 
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 9c4187b..878855a 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -413,41 +413,12 @@
         if (displayId == Display.INVALID_DISPLAY) {
             return false;
         }
-        if (!mUsersOnSecondaryDisplaysEnabled) {
-            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
-        }
 
-        // TODO(b/256242848): temporary workaround to let WM use this API without breaking current
-        // behavior - return true for current user / profile for any display (other than those
-        // explicitly assigned to another users), otherwise they wouldn't be able to launch
-        // activities on other non-passenger displays, like cluster).
-        // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
-        // would be updated by CarService to allow additional mappings.
-        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
-            synchronized (mLock) {
-                boolean assignedToUser = false;
-                boolean assignedToAnotherUser = false;
-                for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
-                    if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
-                        if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
-                            assignedToUser = true;
-                            break;
-                        } else {
-                            assignedToAnotherUser = true;
-                            // Cannot break because it could be assigned to a profile of the user
-                            // (and we better not assume that the iteration will check for the
-                            // parent user before its profiles)
-                        }
-                    }
-                }
-                if (DBG) {
-                    Slogf.d(TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
-                            + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
-                            userId, displayId, assignedToUser, assignedToAnotherUser,
-                            mUsersOnSecondaryDisplays);
-                }
-                return assignedToUser || !assignedToAnotherUser;
-            }
+        if (!mUsersOnSecondaryDisplaysEnabled || displayId == Display.DEFAULT_DISPLAY) {
+            // TODO(b/245939659): will need to move the displayId == Display.DEFAULT_DISPLAY outside
+            // once it supports background users on DEFAULT_DISPLAY (for example, passengers in a
+            // no-driver configuration)
+            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
         }
 
         synchronized (mLock) {
@@ -604,9 +575,7 @@
             ipw.println(mCurrentUserId);
 
             ipw.print("Visible users: ");
-            // TODO: merge 2 lines below if/when IntArray implements toString()...
-            IntArray visibleUsers = getVisibleUsers();
-            ipw.println(java.util.Arrays.toString(visibleUsers.toArray()));
+            ipw.println(getVisibleUsers());
 
             dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
                     "u", "pg");
diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java
index e1026b4..30f2132 100644
--- a/services/core/java/com/android/server/pm/VerificationUtils.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -112,7 +112,7 @@
 
         VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                 verificationCode, null,
-                verifyingSession.mDataLoaderType, verifyingSession.getUser(),
+                verifyingSession.getDataLoaderType(), verifyingSession.getUser(),
                 pms.mContext);
 
         if (state.isInstallAllowed()) {
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 6160519..a54f526 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.Intent.EXTRA_VERSION_CODE;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
@@ -114,30 +115,32 @@
 
     final OriginInfo mOriginInfo;
     final IPackageInstallObserver2 mObserver;
-    final int mInstallFlags;
+    private final int mInstallFlags;
     @NonNull
-    final InstallSource mInstallSource;
-    final String mPackageAbiOverride;
-    final VerificationInfo mVerificationInfo;
-    final SigningDetails mSigningDetails;
+    private final InstallSource mInstallSource;
+    private final String mPackageAbiOverride;
+    private final VerificationInfo mVerificationInfo;
+    private final SigningDetails mSigningDetails;
     @Nullable
     MultiPackageVerifyingSession mParentVerifyingSession;
-    final long mRequiredInstalledVersionCode;
-    final int mDataLoaderType;
-    final int mSessionId;
-    final boolean mUserActionRequired;
-
+    private final long mRequiredInstalledVersionCode;
+    private final int mDataLoaderType;
+    private final int mSessionId;
+    private final boolean mUserActionRequired;
+    private final int mUserActionRequiredType;
     private boolean mWaitForVerificationToComplete;
     private boolean mWaitForIntegrityVerificationToComplete;
     private boolean mWaitForEnableRollbackToComplete;
     private int mRet = PackageManager.INSTALL_SUCCEEDED;
     private String mErrorMessage = null;
+    private final boolean mIsInherit;
+    private final boolean mIsStaged;
 
-    final PackageLite mPackageLite;
+    private final PackageLite mPackageLite;
     private final UserHandle mUser;
     @NonNull
-    final PackageManagerService mPm;
-    final InstallPackageHelper mInstallPackageHelper;
+    private final PackageManagerService mPm;
+    private final InstallPackageHelper mInstallPackageHelper;
 
     VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
@@ -164,6 +167,9 @@
         mSessionId = sessionId;
         mPackageLite = lite;
         mUserActionRequired = userActionRequired;
+        mUserActionRequiredType = sessionParams.requireUserAction;
+        mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+        mIsStaged = sessionParams.isStaged;
     }
 
     @Override
@@ -186,7 +192,7 @@
         // Perform package verification and enable rollback (unless we are simply moving the
         // package).
         if (!mOriginInfo.mExisting) {
-            if ((mInstallFlags & PackageManager.INSTALL_APEX) == 0) {
+            if (!isApex()) {
                 // TODO(b/182426975): treat APEX as APK when APK verification is concerned
                 sendApkVerificationRequest(pkgLite);
             }
@@ -674,10 +680,9 @@
         }
 
         final int installerUid = mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
-        final int installFlags = mInstallFlags;
 
         // Check if installing from ADB
-        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
+        if ((mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
             boolean requestedDisableVerification =
                     (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
             return isAdbVerificationEnabled(pkgInfoLite, userId, requestedDisableVerification);
@@ -685,8 +690,7 @@
 
         // only when not installed from ADB, skip verification for instant apps when
         // the installer and verifier are the same.
-        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
-                && mPm.mInstantAppInstallerActivity != null) {
+        if (isInstant() && mPm.mInstantAppInstallerActivity != null) {
             String installerPackage = mPm.mInstantAppInstallerActivity.packageName;
             for (String requiredVerifierPackage : requiredVerifierPackages) {
                 if (installerPackage.equals(requiredVerifierPackage)) {
@@ -818,6 +822,9 @@
             return;
         }
         sendVerificationCompleteNotification();
+        if (mRet != INSTALL_SUCCEEDED) {
+            PackageMetrics.onVerificationFailed(this);
+        }
     }
 
     private void sendVerificationCompleteNotification() {
@@ -865,4 +872,28 @@
     public UserHandle getUser() {
         return mUser;
     }
+    public int getSessionId() {
+        return mSessionId;
+    }
+    public int getDataLoaderType() {
+        return mDataLoaderType;
+    }
+    public int getUserActionRequiredType() {
+        return mUserActionRequiredType;
+    }
+    public boolean isInstant() {
+        return (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+    }
+    public boolean isInherit() {
+        return mIsInherit;
+    }
+    public int getInstallerPackageUid() {
+        return mInstallSource.mInstallerPackageUid;
+    }
+    public boolean isApex() {
+        return (mInstallFlags & PackageManager.INSTALL_APEX) != 0;
+    }
+    public boolean isStaged() {
+        return mIsStaged;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index 1407530..f388e07 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -303,7 +303,9 @@
     }
 
     private static final Map<Integer, Integer> STATUS_MAP =
-            Map.of(BackgroundDexOptService.STATUS_OK,
+            Map.of(BackgroundDexOptService.STATUS_UNSPECIFIED,
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN,
+                    BackgroundDexOptService.STATUS_OK,
                     ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
                     BackgroundDexOptService.STATUS_ABORT_BY_CANCELLATION,
                     ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION,
@@ -314,7 +316,9 @@
                     BackgroundDexOptService.STATUS_ABORT_BATTERY,
                     ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BATTERY,
                     BackgroundDexOptService.STATUS_DEX_OPT_FAILED,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED);
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
+                    BackgroundDexOptService.STATUS_FATAL_ERROR,
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR);
 
     /** Helper class to write background dexopt job stats to statsd. */
     public static class BackgroundDexoptJobStatsLogger {
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 a7d4cea..558202b 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -452,7 +452,7 @@
             info.category = pkgSetting.getCategoryOverride();
         }
 
-        info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+        info.seInfo = pkgSetting.getSeInfo();
         info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
         info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
 
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
index 944e4ad..876bf17 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
@@ -40,13 +40,6 @@
     String getPrimaryCpuAbi();
 
     /**
-     * @see ApplicationInfo#seInfo
-     * TODO: This field is deriveable and might not have to be cached here.
-     */
-    @Nullable
-    String getSeInfo();
-
-    /**
      * @see ApplicationInfo#secondaryCpuAbi
      */
     @Nullable
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 5b0cc51..c76b129 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
@@ -27,7 +27,6 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.incremental.IncrementalManager;
-import android.text.TextUtils;
 
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.util.ArrayUtils;
@@ -288,16 +287,6 @@
         return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
     }
 
-    public static String getSeInfo(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
-        if (pkgSetting != null) {
-            String overrideSeInfo = pkgSetting.getTransientState().getOverrideSeInfo();
-            if (!TextUtils.isEmpty(overrideSeInfo)) {
-                return overrideSeInfo;
-            }
-        }
-        return ((AndroidPackageHidden) pkg).getSeInfo();
-    }
-
     @Deprecated
     @NonNull
     public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) {
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 a43b979..ba36ab7 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
@@ -462,10 +462,6 @@
     @DataClass.ParcelWith(ForInternedString.class)
     protected String secondaryNativeLibraryDir;
 
-    @Nullable
-    @DataClass.ParcelWith(ForInternedString.class)
-    protected String seInfo;
-
     /**
      * This is an appId, the uid if the userId is == USER_SYSTEM
      */
@@ -1339,6 +1335,11 @@
     }
 
     @Override
+    public UUID getStorageUuid() {
+        return mStorageUuid;
+    }
+
+    @Override
     public int getTargetSandboxVersion() {
         return targetSandboxVersion;
     }
@@ -2905,12 +2906,6 @@
     }
 
     @Override
-    public PackageImpl setSeInfo(@Nullable String seInfo) {
-        this.seInfo = TextUtils.safeIntern(seInfo);
-        return this;
-    }
-
-    @Override
     public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) {
         this.splitCodePaths = splitCodePaths;
         if (splitCodePaths != null) {
@@ -2993,7 +2988,6 @@
         appInfo.primaryCpuAbi = primaryCpuAbi;
         appInfo.secondaryCpuAbi = secondaryCpuAbi;
         appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
-        appInfo.seInfo = seInfo;
         appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR;
         appInfo.uid = uid;
         return appInfo;
@@ -3147,7 +3141,6 @@
         sForInternedString.parcel(this.primaryCpuAbi, dest, flags);
         sForInternedString.parcel(this.secondaryCpuAbi, dest, flags);
         dest.writeString(this.secondaryNativeLibraryDir);
-        dest.writeString(this.seInfo);
         dest.writeInt(this.uid);
         dest.writeLong(this.mBooleans);
         dest.writeLong(this.mBooleans2);
@@ -3307,7 +3300,6 @@
         this.primaryCpuAbi = sForInternedString.unparcel(in);
         this.secondaryCpuAbi = sForInternedString.unparcel(in);
         this.secondaryNativeLibraryDir = in.readString();
-        this.seInfo = in.readString();
         this.uid = in.readInt();
         this.mBooleans = in.readLong();
         this.mBooleans2 = in.readLong();
@@ -3377,12 +3369,6 @@
         return secondaryNativeLibraryDir;
     }
 
-    @Nullable
-    @Override
-    public String getSeInfo() {
-        return seInfo;
-    }
-
     @Override
     public boolean isCoreApp() {
         return getBoolean(Booleans.CORE_APP);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
index d306341..aeaff6d 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
@@ -103,8 +103,6 @@
 
     ParsedPackage setRestrictUpdateHash(byte[] restrictUpdateHash);
 
-    ParsedPackage setSeInfo(String seInfo);
-
     ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir);
 
     /**
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 e3dad45..84907a5 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -34,6 +34,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SigningDetails;
 import android.os.Bundle;
+import android.os.storage.StorageManager;
 import android.processor.immutability.Immutable;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -58,6 +59,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * The representation of an application on disk, as parsed from its split APKs' manifests.
@@ -111,6 +113,13 @@
     String getStaticSharedLibraryName();
 
     /**
+     * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
+     * package was installed.
+     */
+    @NonNull
+    UUID getStorageUuid();
+
+    /**
      * @see ApplicationInfo#targetSdkVersion
      * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
      */
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3c79cdf..e8d0640 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -131,6 +131,14 @@
     String getSecondaryCpuAbi();
 
     /**
+     * @see ApplicationInfo#seInfo
+     * @return The SE info for this package, which may be overridden by a system configured value,
+     * or null if the package isn't available.
+     */
+    @Nullable
+    String getSeInfo();
+
+    /**
      * @see AndroidPackage#isPrivileged()
      */
     boolean isPrivileged();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index c6ce40e..e552a34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -129,6 +129,8 @@
     private final String mPrimaryCpuAbi;
     @Nullable
     private final String mSecondaryCpuAbi;
+    @Nullable
+    private final String mSeInfo;
     private final boolean mHasSharedUser;
     private final int mSharedUserAppId;
     @NonNull
@@ -175,6 +177,7 @@
         mPath = pkgState.getPath();
         mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
         mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+        mSeInfo = pkgState.getSeInfo();
         mHasSharedUser = pkgState.hasSharedUser();
         mSharedUserAppId = pkgState.getSharedUserAppId();
         mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
@@ -542,7 +545,7 @@
         }
 
         @DataClass.Generated(
-                time = 1661977809886L,
+                time = 1665778832625L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
                 inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTime\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -641,6 +644,11 @@
     }
 
     @DataClass.Generated.Member
+    public @Nullable String getSeInfo() {
+        return mSeInfo;
+    }
+
+    @DataClass.Generated.Member
     public boolean isHasSharedUser() {
         return mHasSharedUser;
     }
@@ -697,10 +705,10 @@
     }
 
     @DataClass.Generated(
-            time = 1661977809932L,
+            time = 1665778832668L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index b22c038..57fbfe9 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
+import android.text.TextUtils;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
@@ -62,6 +63,9 @@
     @Nullable
     private String overrideSeInfo;
 
+    @NonNull
+    private String seInfo;
+
     // TODO: Remove in favor of finer grained change notification
     @NonNull
     private final PackageSetting mPackageSetting;
@@ -138,6 +142,7 @@
         this.apkInUpdatedApex = other.apkInUpdatedApex;
         this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
         this.overrideSeInfo = other.overrideSeInfo;
+        this.seInfo = other.seInfo;
         mPackageSetting.onChanged();
     }
 
@@ -206,6 +211,13 @@
         return this;
     }
 
+    @NonNull
+    public PackageStateUnserialized setSeInfo(@NonNull String value) {
+        seInfo = TextUtils.safeIntern(value);
+        mPackageSetting.onChanged();
+        return this;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -271,15 +283,20 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull String getSeInfo() {
+        return seInfo;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull PackageSetting getPackageSetting() {
         return mPackageSetting;
     }
 
     @DataClass.Generated(
-            time = 1661373697219L,
+            time = 1666291743725L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
-            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3aa333a..e9c93ee 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4208,11 +4208,13 @@
             wakeUpFromWakeKey(event);
         }
 
-        if ((result & ACTION_PASS_TO_USER) != 0) {
+        if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
+                && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
             // If the key event is targeted to a specific display, then the user is interacting with
-            // that display. Therefore, give focus to the display that the user is interacting with.
-            if (!mPerDisplayFocusEnabled
-                    && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+            // that display. Therefore, give focus to the display that the user is interacting with,
+            // unless that display maintains its own focus.
+            Display display = mDisplayManager.getDisplay(displayId);
+            if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
                 // An event is targeting a non-focused display. Move the display to top so that
                 // it can become the focused display to interact with the user.
                 // This should be done asynchronously, once the focus logic is fully moved to input
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9281f4b..1ea0988 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2204,6 +2204,15 @@
                     if (sQuiescent) {
                         mDirty |= DIRTY_QUIESCENT;
                     }
+                    PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+                    if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+                        // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+                        // half off" state when a non-default-group VirtualDisplay causes the global
+                        // wakefulness to change to awake, even though the default display is
+                        // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+                        // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+                        defaultGroup.setSandmanSummonedLocked(true);
+                    }
                     break;
 
                 case WAKEFULNESS_ASLEEP:
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index fbb6644..f17e5e7 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -43,6 +43,43 @@
     public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
 
     /**
+     * Creates a sensor that is registered at runtime by the system with the sensor service.
+     *
+     * The runtime sensors created here are different from the
+     * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
+     * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
+     * sensors that belong to an external (virtual) device.
+     *
+     * @param deviceId The identifier of the device this sensor is associated with.
+     * @param type The generic type of the sensor.
+     * @param name The name of the sensor.
+     * @param vendor The vendor string of the sensor.
+     * @param callback The callback to get notified when the sensor listeners have changed.
+     * @return The sensor handle.
+     */
+    public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+            @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+
+    /**
+     * Unregisters the sensor with the given handle from the framework.
+     */
+    public abstract void removeRuntimeSensor(int handle);
+
+    /**
+     * Sends an event for the runtime sensor with the given handle to the framework.
+     *
+     * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+     *
+     * @param handle The sensor handle.
+     * @param type The type of the sensor.
+     * @param timestampNanos When the event occurred.
+     * @param values The values of the event.
+     * @return Whether the event injection was successful.
+     */
+    public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
+            @NonNull float[] values);
+
+    /**
      * Listener for proximity sensor state changes.
      */
     public interface ProximityActiveListener {
@@ -52,4 +89,17 @@
          */
         void onProximityActive(boolean isActive);
     }
+
+    /**
+     * Callback for runtime sensor state changes. Only relevant to sensors created via
+     * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
+     * not covered.
+     */
+    public interface RuntimeSensorStateChangeCallback {
+        /**
+         * Invoked when the listeners of the runtime sensor have changed.
+         */
+        void onStateChanged(boolean enabled, int samplingPeriodMicros,
+                int batchReportLatencyMicros);
+    }
 }
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 8fe2d52..d8e3bdd 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -29,7 +29,9 @@
 import com.android.server.SystemService;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
@@ -40,6 +42,8 @@
     private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
             new ArrayMap<>();
     @GuardedBy("mLock")
+    private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
+    @GuardedBy("mLock")
     private Future<?> mSensorServiceStart;
     @GuardedBy("mLock")
     private long mPtr;
@@ -51,6 +55,12 @@
     private static native void registerProximityActiveListenerNative(long ptr);
     private static native void unregisterProximityActiveListenerNative(long ptr);
 
+    private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
+            String name, String vendor,
+            SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+    private static native void unregisterRuntimeSensorNative(long ptr, int handle);
+    private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
+            long timestampNanos, float[] values);
 
     public SensorService(Context ctx) {
         super(ctx);
@@ -85,6 +95,38 @@
 
     class LocalService extends SensorManagerInternal {
         @Override
+        public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+                @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+            synchronized (mLock) {
+                int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+                        callback);
+                mRuntimeSensorHandles.add(handle);
+                return handle;
+            }
+        }
+
+        @Override
+        public void removeRuntimeSensor(int handle) {
+            synchronized (mLock) {
+                if (mRuntimeSensorHandles.contains(handle)) {
+                    mRuntimeSensorHandles.remove(handle);
+                    unregisterRuntimeSensorNative(mPtr, handle);
+                }
+            }
+        }
+
+        @Override
+        public boolean sendSensorEvent(int handle, int type, long timestampNanos,
+                @NonNull float[] values) {
+            synchronized (mLock) {
+                if (!mRuntimeSensorHandles.contains(handle)) {
+                    return false;
+                }
+                return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
+            }
+        }
+
+        @Override
         public void addProximityActiveListener(@NonNull Executor executor,
                 @NonNull ProximityActiveListener listener) {
             Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 0409a84..111b4f6 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -109,7 +109,7 @@
      * testing only. See {@link #isGeoDetectionExecutionEnabled()} and {@link #getDetectionMode()}
      * for details.
      */
-    boolean getGeoDetectionRunInBackgroundEnabled() {
+    boolean getGeoDetectionRunInBackgroundEnabledSetting() {
         return mGeoDetectionRunInBackgroundEnabled;
     }
 
@@ -132,7 +132,7 @@
      * from the raw setting value.
      */
     public boolean getAutoDetectionEnabledBehavior() {
-        return isAutoDetectionSupported() && mAutoDetectionEnabledSetting;
+        return isAutoDetectionSupported() && getAutoDetectionEnabledSetting();
     }
 
     /** Returns the ID of the user this configuration is associated with. */
@@ -171,27 +171,55 @@
      * time zone.
      */
     public @DetectionMode int getDetectionMode() {
-        if (!getAutoDetectionEnabledBehavior()) {
+        if (!isAutoDetectionSupported()) {
+            // Handle the easy case first: No auto detection algorithms supported must mean manual.
             return DETECTION_MODE_MANUAL;
-        } else if (isGeoDetectionSupported() && getLocationEnabledSetting()
-                && getGeoDetectionEnabledSetting()) {
+        } else if (!getAutoDetectionEnabledSetting()) {
+            // Auto detection algorithms are supported, but disabled by the user.
+            return DETECTION_MODE_MANUAL;
+        } else if (getGeoDetectionEnabledBehavior()) {
             return DETECTION_MODE_GEO;
-        } else {
+        } else if (isTelephonyDetectionSupported()) {
             return DETECTION_MODE_TELEPHONY;
+        } else {
+            // On devices with telephony detection support, telephony is used instead of geo when
+            // geo cannot be used. This "unknown" case can occur on devices with only the location
+            // detection algorithm supported when the user's master location setting prevents its
+            // use.
+            return DETECTION_MODE_UNKNOWN;
         }
     }
 
+    private boolean getGeoDetectionEnabledBehavior() {
+        // isAutoDetectionSupported() should already have been checked before calling this method.
+        if (isGeoDetectionSupported() && getLocationEnabledSetting()) {
+            if (isTelephonyDetectionSupported()) {
+                // This is the "normal" case for smartphones that have both telephony and geo
+                // detection: the user chooses which type of detection to use.
+                return getGeoDetectionEnabledSetting();
+            } else {
+                // When only geo detection is supported then there is no choice for the user to
+                // make between detection modes, so no user setting is consulted.
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns true if geolocation time zone detection behavior can execute. Typically, this will
      * agree with {@link #getDetectionMode()}, but under rare circumstances the geolocation detector
-     * may be run in the background if the user's settings allow. See also {@link
-     * #getGeoDetectionRunInBackgroundEnabled()}.
+     * may be run in the background if the user's settings allow.
      */
     public boolean isGeoDetectionExecutionEnabled() {
+        return getDetectionMode() == DETECTION_MODE_GEO
+                || getGeoDetectionRunInBackgroundEnabledBehavior();
+    }
+
+    private boolean getGeoDetectionRunInBackgroundEnabledBehavior() {
         return isGeoDetectionSupported()
                 && getLocationEnabledSetting()
-                && ((mAutoDetectionEnabledSetting && getGeoDetectionEnabledSetting())
-                || getGeoDetectionRunInBackgroundEnabled());
+                && getGeoDetectionRunInBackgroundEnabledSetting();
     }
 
     @NonNull
@@ -216,11 +244,19 @@
         builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
 
         boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
+        boolean deviceHasTelephonyDetection = isTelephonyDetectionSupported();
+
         // Note: allowConfigDateTime does not restrict the ability to change location time zone
         // detection enabled. This is intentional as it has user privacy implications and so it
-        // makes sense to leave this under a user's control.
+        // makes sense to leave this under a user's control. The only time this is not true is
+        // on devices that only support location-based detection and the main auto detection setting
+        // is used to influence whether location can be used.
         final @CapabilityState int configureGeolocationDetectionEnabledCapability;
-        if (!deviceHasLocationTimeZoneDetection) {
+        if (!deviceHasLocationTimeZoneDetection || !deviceHasTelephonyDetection) {
+            // If the device doesn't have geolocation detection support OR it ONLY has geolocation
+            // detection support (no telephony) then the user doesn't need the ability to toggle the
+            // location-based detection on and off (the auto detection toggle is considered
+            // sufficient).
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
         } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index aad5359..59691f8 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -136,7 +136,7 @@
      * testing only.
      */
     public boolean getGeoDetectionRunInBackgroundEnabled() {
-        return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabled();
+        return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabledSetting();
     }
 
     /** Returns true if enhanced metric collection is enabled. */
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 295c5c8a..6ebaf14c 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -279,15 +279,18 @@
             final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
             setAutoDetectionEnabledIfRequired(autoDetectionEnabled);
 
-            // Avoid writing the geo detection enabled setting for devices with settings that
-            // are currently overridden by server flags: otherwise we might overwrite a droidfood
-            // user's real setting permanently.
-            // Also avoid writing the geo detection enabled setting for devices that do not support
-            // geo time zone detection: if we wrote it down then we'd set the value explicitly,
-            // which would prevent detecting "default" later. That might influence what happens on
-            // later releases that start to support geo detection on the same hardware.
+            // Only write the geo detection enabled setting when its values is used, e.g.:
+            // 1) Devices with a setting value that is not currently overridden by server flags
+            // 2) Devices that support both telephony and location detection algorithms
+            //
+            // If we wrote a setting value down when it's not used then we'd be setting the value
+            // explicitly, which would prevent detecting the setting is in "default" state later.
+            // Not being able to detect if the user has actually expressed a preference could
+            // influence what happens on later releases that start to support geo detection on the
+            // user's same hardware.
             if (!getGeoDetectionSettingEnabledOverride().isPresent()
-                    && isGeoTimeZoneDetectionFeatureSupported()) {
+                    && isGeoTimeZoneDetectionFeatureSupported()
+                    && isTelephonyTimeZoneDetectionFeatureSupported()) {
                 final boolean geoDetectionEnabledSetting = configuration.isGeoDetectionEnabled();
                 setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting);
             }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index f8c1c92..10cd5d1 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -91,7 +91,7 @@
             deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() {
                 @Override
                 public void onFlightComplete() {
-                    timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+                    timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback("onFlightComplete()");
                 }
             });
 
@@ -402,9 +402,9 @@
      * Sends a signal to enable telephony fallback. Provided for command-line access for use
      * during tests. This is not exposed as a binder API.
      */
-    void enableTelephonyFallback() {
+    void enableTelephonyFallback(@NonNull String reason) {
         enforceManageTimeZoneDetectorPermission();
-        mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+        mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(reason);
     }
 
     /**
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 69274db..ab68e83 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -189,7 +189,7 @@
     }
 
     private int runEnableTelephonyFallback() {
-        mInterface.enableTelephonyFallback();
+        mInterface.enableTelephonyFallback("Command line");
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 5768a6b..37e67c9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -35,13 +35,13 @@
  * <p>Devices can have zero, one or two automatic time zone detection algorithms available at any
  * point in time.
  *
- * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
+ * <p>The two automatic detection algorithms supported are "telephony" and "location". Algorithm
  * availability and use depends on several factors:
  * <ul>
  * <li>Telephony is only available on devices with a telephony stack.
- * <li>Geolocation is also optional and configured at image creation time. When enabled on a
- * device, its availability depends on the current user's settings, so switching between users can
- * change the automatic algorithm used by the device.</li>
+ * <li>Location is also optional and configured at image creation time. When enabled on a device,
+ * its availability depends on the current user's settings, so switching between users can change
+ * the automatic detection algorithm used by the device.</li>
  * </ul>
  *
  * <p>If there are no automatic time zone detections algorithms available then the user can usually
@@ -56,14 +56,14 @@
  * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
  * suggestion otherwise it will remain in use.
  *
- * <p>Geolocation detection is dependent on the current user and their settings. The device retains
- * at most one geolocation suggestion. Generally, use of a device's location is dependent on the
- * user's "location toggle", but even when that is enabled the user may choose to enable / disable
- * the use of geolocation for device time zone detection. If the current user changes to one that
- * does not have geolocation detection enabled, or the user turns off geolocation detection, then
- * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
- * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
- * will remain in use.
+ * <p>Location-based detection is dependent on the current user and their settings. The device
+ * retains at most one geolocation suggestion. Generally, use of a device's location is dependent on
+ * the user's "location toggle", but even when that is enabled the user may choose to enable /
+ * disable the use of location for device time zone detection. If the current user changes to one
+ * that does not have location-based detection enabled, or the user turns off the location-based
+ * detection, then the strategy will be sent an event that clears the latest suggestion. Devices
+ * that lose their location fix must have an empty suggestion submitted in order to "withdraw" their
+ * previous suggestion otherwise it will remain in use.
  *
  * <p>The strategy uses only one algorithm at a time and does not attempt consensus even when
  * more than one is available on a device. This "use only one" behavior is deliberate as different
@@ -72,25 +72,27 @@
  * users enter areas without the necessary signals. Ultimately, with no perfect algorithm available,
  * the user is left to choose which algorithm works best for their circumstances.
  *
- * <p>When geolocation detection is supported and enabled, in certain circumstances, such as during
- * international travel, it makes sense to prioritize speed of detection via telephony (when
- * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can
- * sometimes be slow to get a location fix and can require network connectivity (which cannot be
- * assumed when users are travelling) for server-assisted location detection or time zone lookup.
- * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms,
- * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device
- * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in
- * response to signals outside of the scope of this class. Telephony fallback allows the use of
- * telephony suggestions to help with faster detection but only until geolocation detection
- * provides a concrete, "certain" suggestion. After geolocation has made the first certain
- * suggestion, telephony fallback is disabled until the next call to {@link
- * #enableTelephonyTimeZoneFallback()}.
+ * <p>When the location detection algorithm is supported and enabled, in certain circumstances, such
+ * as during international travel, it makes sense to prioritize speed of detection via telephony
+ * (when available) Vs waiting for the location-based detection algorithm to reach certainty.
+ * Location-based detection can sometimes be slow to get a location fix and can require network
+ * connectivity (which cannot be assumed when users are travelling) for server-assisted location
+ * detection or time zone lookup. Therefore, as a restricted form of prioritization between location
+ * and telephony algorithms, the strategy provides "telephony fallback mode" behavior, which can be
+ * set to "supported" via device config. Fallback mode is entered at runtime in response to signals
+ * from outside of the strategy, e.g. from a call to {@link
+ * #enableTelephonyTimeZoneFallback(String)}, or from information in the latest {@link
+ * LocationAlgorithmEvent}. For telephony fallback mode to actually use a telephony suggestion, the
+ * location algorithm <em>must</em> report it is uncertain. Telephony fallback allows the use of
+ * telephony suggestions to help with faster detection but only until the location algorithm
+ * provides a concrete, "certain" suggestion. After the location algorithm has made a certain
+ * suggestion, telephony fallback mode is disabled.
  *
  * <p>Threading:
  *
  * <p>Implementations of this class must be thread-safe as calls calls like {@link
  * #generateMetricsState()} and {@link #dump(IndentingPrintWriter, String[])} may be called on
- * differents thread concurrently with other operations.
+ * different threads concurrently with other operations.
  *
  * @hide
  */
@@ -181,11 +183,11 @@
     void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
 
     /**
-     * Tells the strategy that it can fall back to telephony detection while geolocation detection
-     * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can
-     * disable it again. See {@link TimeZoneDetectorStrategy} for details.
+     * Tells the strategy that it can fall back to telephony detection while the location detection
+     * algorithm remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)}
+     * can disable it again. See {@link TimeZoneDetectorStrategy} for details.
      */
-    void enableTelephonyTimeZoneFallback();
+    void enableTelephonyTimeZoneFallback(@NonNull String reason);
 
     /** Generates a state snapshot for metrics. */
     @NonNull
@@ -194,6 +196,6 @@
     /** Returns {@code true} if the device supports telephony time zone detection. */
     boolean isTelephonyTimeZoneDetectionSupported();
 
-    /** Returns {@code true} if the device supports geolocation time zone detection. */
+    /** Returns {@code true} if the device supports location-based time zone detection. */
     boolean isGeoTimeZoneDetectionSupported();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 3424251..e0e3565 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -47,6 +47,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import com.android.server.timezonedetector.ConfigurationInternal.DetectionMode;
 
 import java.io.PrintWriter;
 import java.time.Duration;
@@ -62,12 +63,8 @@
 public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
 
     /**
-     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
-     * / system properties. It can be faked for testing.
-     *
-     * <p>Note: Because the settings / system properties-derived values can currently be modified
-     * independently and from different threads (and processes!), their use is prone to race
-     * conditions.
+     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that
+     * available from {@link #mServiceConfigAccessor}. It can be faked for testing.
      */
     @VisibleForTesting
     public interface Environment {
@@ -233,7 +230,7 @@
      * allows).
      *
      * <p>This field is only actually used when telephony time zone fallback is supported, but the
-     * value is maintained even when it isn't supported as it can be turned on at any time via
+     * value is maintained even when it isn't supported as support can be turned on at any time via
      * server flags. The elapsed realtime when the mode last changed is used to help ordering
      * between fallback mode switches and suggestions.
      *
@@ -420,10 +417,15 @@
             notifyStateChangeListenersAsynchronously();
         }
 
-        // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
-        // will usually disable telephony fallback mode if it is currently enabled.
-        // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback.
-        disableTelephonyFallbackIfNeeded();
+        // Manage telephony fallback state.
+        if (event.getAlgorithmStatus().couldEnableTelephonyFallback()) {
+            // An event may trigger entry into telephony fallback mode if the status
+            // indicates the location algorithm cannot work and is likely to stay not working.
+            enableTelephonyTimeZoneFallback("handleLocationAlgorithmEvent(), event=" + event);
+        } else {
+            // A certain suggestion will exit telephony fallback mode.
+            disableTelephonyFallbackIfNeeded();
+        }
 
         // Now perform auto time zone detection. The new event may be used to modify the time zone
         // setting.
@@ -496,38 +498,41 @@
     }
 
     @Override
-    public synchronized void enableTelephonyTimeZoneFallback() {
-        // Only do any work if fallback is currently not enabled.
+    public synchronized void enableTelephonyTimeZoneFallback(@NonNull String reason) {
+        // Only do any work to enter fallback mode if fallback is currently not already enabled.
         if (!mTelephonyTimeZoneFallbackEnabled.getValue()) {
             ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
             final boolean fallbackEnabled = true;
             mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
                     mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
 
-            String logMsg = "enableTelephonyTimeZoneFallbackMode: "
-                    + " currentUserConfig=" + currentUserConfig
-                    + ", mTelephonyTimeZoneFallbackEnabled="
-                    + mTelephonyTimeZoneFallbackEnabled;
+            String logMsg = "enableTelephonyTimeZoneFallback: "
+                    + " reason=" + reason
+                    + ", currentUserConfig=" + currentUserConfig
+                    + ", mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled;
             logTimeZoneDebugInfo(logMsg);
 
             // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact.
-            // If the latest event contains a "certain" geolocation suggestion, then the telephony
-            // fallback value needs to be considered after changing it.
+            // If the latest location algorithm event contains a "certain" geolocation suggestion,
+            // then the telephony fallback mode needs to be (re)considered after changing it.
+            //
             // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
             // above, and the fact that geolocation suggestions should never have a time in the
-            // future, the following call will be a no-op, and telephony fallback will remain
-            // enabled. This comment / call is left as a reminder that it is possible for there to
-            // be a current, "certain" geolocation suggestion when this signal arrives and it is
-            // intentional that fallback stays enabled in this case. The choice to do this
-            // is mostly for symmetry WRT the case where fallback is enabled and an old "certain"
-            // geolocation is received; that would also leave telephony fallback enabled.
-            // This choice means that telephony fallback will remain enabled until a new "certain"
-            // geolocation suggestion is received. If, instead, the next geolocation is "uncertain",
-            // then telephony fallback will occur.
+            // future, the following call will usually be a no-op, and telephony fallback mode will
+            // remain enabled. This comment / call is left as a reminder that it is possible in some
+            // cases for there to be a current, "certain" geolocation suggestion when an attempt is
+            // made to enable telephony fallback mode and it is intentional that fallback mode stays
+            // enabled in this case. The choice to do this is mostly for symmetry WRT the case where
+            // fallback is enabled and then an old "certain" geolocation suggestion is received;
+            // that would also leave telephony fallback mode enabled.
+            //
+            // This choice means that telephony fallback mode remains enabled if there is an
+            // existing "certain" suggestion until a new "certain" geolocation suggestion is
+            // received. If, instead, the next geolocation suggestion is "uncertain", then telephony
+            // fallback, i.e. the use of a telephony suggestion, will actually occur.
             disableTelephonyFallbackIfNeeded();
 
             if (currentUserConfig.isTelephonyFallbackSupported()) {
-                String reason = "enableTelephonyTimeZoneFallbackMode";
                 doAutoTimeZoneDetection(currentUserConfig, reason);
             }
         }
@@ -597,9 +602,10 @@
     @GuardedBy("this")
     private void doAutoTimeZoneDetection(
             @NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
-        // Use the correct algorithm based on the user's current configuration. If it changes, then
-        // detection will be re-run.
-        switch (currentUserConfig.getDetectionMode()) {
+        // Use the correct detection algorithm based on the device's config and the user's current
+        // configuration. If user config changes, then detection will be re-run.
+        @DetectionMode int detectionMode = currentUserConfig.getDetectionMode();
+        switch (detectionMode) {
             case ConfigurationInternal.DETECTION_MODE_MANUAL:
                 // No work to do.
                 break;
@@ -635,9 +641,14 @@
             case ConfigurationInternal.DETECTION_MODE_TELEPHONY:
                 doTelephonyTimeZoneDetection(detectionReason);
                 break;
+            case ConfigurationInternal.DETECTION_MODE_UNKNOWN:
+                // The "DETECTION_MODE_UNKNOWN" state can occur on devices with only location
+                // detection algorithm support and when the user's master location toggle is off.
+                Slog.i(LOG_TAG, "Unknown detection mode: " + detectionMode + ", is location off?");
+                break;
             default:
-                Slog.wtf(LOG_TAG, "Unknown detection mode: "
-                        + currentUserConfig.getDetectionMode());
+                // Coding error
+                Slog.wtf(LOG_TAG, "Unknown detection mode: " + detectionMode);
         }
     }
 
@@ -1043,15 +1054,31 @@
         TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
                 createTelephonyAlgorithmStatus(currentConfigurationInternal);
 
-        LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
-                latestLocationAlgorithmEvent == null ? LocationTimeZoneAlgorithmStatus.UNKNOWN
-                        : latestLocationAlgorithmEvent.getAlgorithmStatus();
+        LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = createLocationAlgorithmStatus(
+                currentConfigurationInternal, latestLocationAlgorithmEvent);
 
         return new TimeZoneDetectorStatus(
                 detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus);
     }
 
     @NonNull
+    private static LocationTimeZoneAlgorithmStatus createLocationAlgorithmStatus(
+            ConfigurationInternal currentConfigurationInternal,
+            LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+        LocationTimeZoneAlgorithmStatus locationAlgorithmStatus;
+        if (latestLocationAlgorithmEvent != null) {
+            locationAlgorithmStatus = latestLocationAlgorithmEvent.getAlgorithmStatus();
+        } else if (!currentConfigurationInternal.isGeoDetectionSupported()) {
+            locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.NOT_SUPPORTED;
+        } else if (currentConfigurationInternal.isGeoDetectionExecutionEnabled()) {
+            locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.RUNNING_NOT_REPORTED;
+        } else {
+            locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.NOT_RUNNING;
+        }
+        return locationAlgorithmStatus;
+    }
+
+    @NonNull
     private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus(
             @NonNull ConfigurationInternal currentConfigurationInternal) {
         int algorithmStatus;
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 0b1f6b9..f971db9 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
     // Trust state
     private boolean mTrusted;
     private boolean mWaitingForTrustableDowngrade = false;
+    private boolean mWithinSecurityLockdownWindow = false;
     private boolean mTrustable;
     private CharSequence mMessage;
     private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
                         mWaitingForTrustableDowngrade = true;
+                        setSecurityWindowTimer();
                     } else {
                         mWaitingForTrustableDowngrade = false;
                     }
@@ -452,6 +454,9 @@
             if (mBound) {
                 scheduleRestart();
             }
+            if (mWithinSecurityLockdownWindow) {
+                mTrustManagerService.lockUser(mUserId);
+            }
             // mTrustDisabledByDpm maintains state
         }
     };
@@ -673,6 +678,22 @@
         }
     }
 
+    private void setSecurityWindowTimer() {
+        mWithinSecurityLockdownWindow = true;
+        long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+        mAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                expiration,
+                TAG,
+                new AlarmManager.OnAlarmListener() {
+                    @Override
+                    public void onAlarm() {
+                        mWithinSecurityLockdownWindow = false;
+                    }
+                },
+                Handler.getMain());
+    }
+
     public boolean isManagingTrust() {
         return mManagingTrust && !mTrustDisabledByDpm;
     }
@@ -691,7 +712,6 @@
 
     public void destroy() {
         mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
         if (!mBound) {
             return;
         }
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index e88ac63..6efbd89 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -162,7 +162,7 @@
     }
 
     /**
-     * Logs a {@link Log.VEBOSE} message with an exception
+     * Logs a {@link Log.VEBOSE} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -170,10 +170,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void v(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.VERBOSE)) return;
 
-        v(tag, getMessage(format, args), exception);
+        v(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -192,7 +192,7 @@
     }
 
     /**
-     * Logs a {@link Log.DEBUG} message with an exception
+     * Logs a {@link Log.DEBUG} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -200,10 +200,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void d(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.DEBUG)) return;
 
-        d(tag, getMessage(format, args), exception);
+        d(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -222,7 +222,7 @@
     }
 
     /**
-     * Logs a {@link Log.INFO} message with an exception
+     * Logs a {@link Log.INFO} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -230,10 +230,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void i(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.INFO)) return;
 
-        i(tag, getMessage(format, args), exception);
+        i(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -252,7 +252,7 @@
     }
 
     /**
-     * Logs a {@link Log.WARN} message with an exception
+     * Logs a {@link Log.WARN} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
      * enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -260,10 +260,10 @@
      * calling this method in a critical path, make sure to explicitly do the check before calling
      * it.
      */
-    public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.WARN)) return;
 
-        w(tag, getMessage(format, args), exception);
+        w(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -282,7 +282,7 @@
     }
 
     /**
-     * Logs a {@link Log.ERROR} message with an exception
+     * Logs a {@link Log.ERROR} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
      * enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -290,10 +290,10 @@
      * calling this method in a critical path, make sure to explicitly do the check before calling
      * it.
      */
-    public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.ERROR)) return;
 
-        e(tag, getMessage(format, args), exception);
+        e(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -304,11 +304,11 @@
     }
 
     /**
-     * Logs a {@code wtf} message with an exception.
+     * Logs a {@code wtf} message with a throwable.
      */
-    public static void wtf(String tag, Exception exception, String format,
+    public static void wtf(String tag, Throwable throwable, String format,
             @Nullable Object... args) {
-        wtf(tag, getMessage(format, args), exception);
+        wtf(tag, getMessage(format, args), throwable);
     }
 
     private static String getMessage(String format, @Nullable Object... args) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f74956b..5d08461 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1559,8 +1559,9 @@
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
-                        Binder.restoreCallingIdentity(ident);
                         Slog.d(TAG, "failed to send callback!", e);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
                     }
                     t.traceEnd();
                     mReply = null;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 530fa4d..12424c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3317,9 +3317,17 @@
                 mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
                         resultTo.getUriPermissionsLocked());
             }
-            if (mForceSendResultForMediaProjection) {
-                resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
-                        resultData, resultGrants, true /* forceSendForMediaProjection */);
+            if (mForceSendResultForMediaProjection || resultTo.isState(RESUMED)) {
+                // Sending the result to the resultTo activity asynchronously to prevent the
+                // resultTo activity getting results before this Activity paused.
+                final ActivityRecord resultToActivity = resultTo;
+                mAtmService.mH.post(() -> {
+                    synchronized (mAtmService.mGlobalLock) {
+                        resultToActivity.sendResult(this.getUid(), resultWho, requestCode,
+                                resultCode, resultData, resultGrants,
+                                mForceSendResultForMediaProjection);
+                    }
+                });
             } else {
                 resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
             }
@@ -4630,7 +4638,7 @@
                 false /* forceSendForMediaProjection */);
     }
 
-    private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
+    void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
             Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
         if (callingUid > 0) {
             mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5938e7f..0b16a4d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -78,7 +78,6 @@
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -2777,11 +2776,6 @@
                 errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
                 break;
             }
-            case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: {
-                errMsg = "Cannot embed activity across TaskFragments for result, resultTo: "
-                        + mStartActivity.resultTo;
-                break;
-            }
             default:
                 errMsg = "Unhandled embed result:" + result;
         }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 3bb0238..798e739 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -226,9 +227,8 @@
 
             mBackAnimationInProgress = true;
             // We don't have an application callback, let's find the destination of the back gesture
-            Task finalTask = currentTask;
-            prevActivity = currentTask.getActivity(
-                    (r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
+            // The search logic should align with ActivityClientController#finishActivity
+            prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
             // TODO Dialog window does not need to attach on activity, check
             // window.mAttrs.type != TYPE_BASE_APPLICATION
             if ((window.getParent().getChildCount() > 1
@@ -241,15 +241,18 @@
                 // We have another Activity in the same currentTask to go to
                 backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
                 removedWindowContainer = currentActivity;
+                prevTask = prevActivity.getTask();
             } else if (currentTask.returnsToHomeRootTask()) {
                 // Our Task should bring back to home
                 removedWindowContainer = currentTask;
+                prevTask = currentTask.getDisplayArea().getRootHomeTask();
                 backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
                 mShowWallpaper = true;
             } else if (currentActivity.isRootOfTask()) {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
-                prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
+                prevTask = currentTask.mRootWindowContainer.getTask(Task::showToCurrentUser,
+                        currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
                 removedWindowContainer = currentTask;
                 // If it reaches the top activity, we will check the below task from parent.
                 // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
@@ -423,6 +426,11 @@
 
         void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) {
             clearBackAnimateTarget(null);
+            if (close == null || open == null) {
+                Slog.e(TAG, "reset animation with null target close: "
+                        + close + " open: " + open);
+                return;
+            }
             if (close.asActivityRecord() != null && open.asActivityRecord() != null
                     && (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
                 mSwitchType = ACTIVITY_SWITCH;
@@ -601,26 +609,23 @@
                 // reset leash after animation finished.
                 leashes.add(screenshotSurface);
             }
-        } else if (prevTask != null) {
-            prevActivity = prevTask.getTopNonFinishingActivity();
-            if (prevActivity != null) {
-                // Make previous task show from behind by marking its top activity as visible
-                // and launch-behind to bump its visibility for the duration of the back gesture.
-                setLaunchBehind(prevActivity);
+        } else if (prevTask != null && prevActivity != null) {
+            // Make previous task show from behind by marking its top activity as visible
+            // and launch-behind to bump its visibility for the duration of the back gesture.
+            setLaunchBehind(prevActivity);
 
-                final SurfaceControl leash = prevActivity.makeAnimationLeash()
-                        .setName("BackPreview Leash for " + prevActivity)
-                        .setHidden(false)
-                        .build();
-                prevActivity.reparentSurfaceControl(startedTransaction, leash);
-                behindAppTarget = createRemoteAnimationTargetLocked(
-                        prevTask, leash, MODE_OPENING);
+            final SurfaceControl leash = prevActivity.makeAnimationLeash()
+                    .setName("BackPreview Leash for " + prevActivity)
+                    .setHidden(false)
+                    .build();
+            prevActivity.reparentSurfaceControl(startedTransaction, leash);
+            behindAppTarget = createRemoteAnimationTargetLocked(
+                    prevTask, leash, MODE_OPENING);
 
-                // reset leash after animation finished.
-                leashes.add(leash);
-                prevActivity.reparentSurfaceControl(finishedTransaction,
-                        prevActivity.getParentSurfaceControl());
-            }
+            // reset leash after animation finished.
+            leashes.add(leash);
+            prevActivity.reparentSurfaceControl(finishedTransaction,
+                    prevActivity.getParentSurfaceControl());
         }
 
         if (mShowWallpaper) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0119e4d..9c920f53 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3380,7 +3380,7 @@
                     }
                 }
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-                controller.mTransitionMetricsReporter.associate(t,
+                controller.mTransitionMetricsReporter.associate(t.getToken(),
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
                 startAsyncRotation(false /* shouldDebounce */);
             }
@@ -3683,7 +3683,7 @@
      * @return The focused window or null if there isn't any or no need to seek.
      */
     WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
-        return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
+        return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
                     ? findFocusedWindow() : null;
     }
 
@@ -6315,6 +6315,14 @@
     }
 
     /**
+     * @return whether this display maintains its own focus and touch mode.
+     */
+    boolean hasOwnFocus() {
+        return mWmService.mPerDisplayFocusEnabled
+                || (mDisplayInfo.flags & Display.FLAG_OWN_FOCUS) != 0;
+    }
+
+    /**
      * @return whether the keyguard is occluded on this display
      */
     boolean isKeyguardOccluded() {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 7860b15..3e1105b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -270,7 +270,7 @@
                 InputConfigAdapter.getMask());
 
         final boolean focusable = w.canReceiveKeys()
-                && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
+                && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop());
         inputWindowHandle.setFocusable(focusable);
 
         final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 2dbccae..bb4c482 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
     private final LetterboxConfiguration mLetterboxConfiguration;
     private final ActivityRecord mActivityRecord;
 
-    // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
-    // corners above the taskbar.
-    private final float mExpandedTaskBarHeight;
-
     private boolean mShowWallpaperForLetterboxBackground;
 
     @Nullable
@@ -102,8 +98,6 @@
         // is created in its constructor. It shouldn't be used in this constructor but it's safe
         // to use it after since controller is only used in ActivityRecord.
         mActivityRecord = activityRecord;
-        mExpandedTaskBarHeight =
-                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
     }
 
     float getSplitScreenAspectRatio() {
+        // Getting the same aspect ratio that apps get in split screen.
+        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+        if (displayContent == null) {
+            return getDefaultMinAspectRatioForUnresizableApps();
+        }
         int dividerWindowWidth =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
         int dividerInsets =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
         int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
-        // Getting the same aspect ratio that apps get in split screen.
-        Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+        final Rect bounds = new Rect(displayContent.getBounds());
         if (bounds.width() >= bounds.height()) {
             bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
             bounds.right = bounds.centerX();
@@ -555,7 +552,6 @@
         final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
 
         return taskbarInsetsSource != null
-                && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
                 && taskbarInsetsSource.isVisible();
     }
 
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 94d4dde..2e5ab1a 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -441,6 +441,7 @@
                         .setPixelFormat(PixelFormat.RGBA_8888)
                         .setChildrenOnly(true)
                         .setAllowProtected(true)
+                        .setCaptureSecureLayers(true)
                         .build();
         final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
                 ScreenCapture.captureLayers(captureArgs);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 91cb037..d3a3cf5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -172,13 +172,6 @@
      * indicate that an Activity can't be embedded because the Activity is started on a new task.
      */
     static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
-    /**
-     * An embedding check result of
-     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
-     * indicate that an Activity can't be embedded because the Activity is started on a new
-     * TaskFragment, e.g. start an Activity on a new TaskFragment for result.
-     */
-    static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4;
 
     /**
      * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
@@ -189,7 +182,6 @@
             EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
             EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
             EMBEDDING_DISALLOWED_NEW_TASK,
-            EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
     })
     @interface EmbeddingCheckResult {}
 
@@ -616,14 +608,6 @@
             return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
         }
 
-        // Cannot embed activity across TaskFragments for activity result.
-        // If the activity that started for result is finishing, it's likely that this start mode
-        // is used to place an activity in the same task. Since the finishing activity won't be
-        // able to get the results, so it's OK to embed in a different TaskFragment.
-        if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) {
-            return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
-        }
-
         return EMBEDDING_ALLOWED;
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b277804..9cb13e4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -91,6 +91,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -100,7 +101,7 @@
  * Represents a logical transition.
  * @see TransitionController
  */
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
     private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
 
@@ -151,6 +152,7 @@
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
+    private final Token mToken;
     private RemoteTransition mRemoteTransition = null;
 
     /** Only use for clean-up after binder death! */
@@ -213,10 +215,26 @@
         mFlags = flags;
         mController = controller;
         mSyncEngine = syncEngine;
+        mToken = new Token(this);
 
         controller.mTransitionTracer.logState(this);
     }
 
+    @Nullable
+    static Transition fromBinder(@NonNull IBinder token) {
+        try {
+            return ((Token) token).mTransition.get();
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Invalid transition token: " + token, e);
+            return null;
+        }
+    }
+
+    @NonNull
+    IBinder getToken() {
+        return mToken;
+    }
+
     void addFlag(int flag) {
         mFlags |= flag;
     }
@@ -726,6 +744,11 @@
             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                     System.identityHashCode(this));
         }
+        // Close the transactions now. They were originally copied to Shell in case we needed to
+        // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+        // immediately.
+        if (mStartTransaction != null) mStartTransaction.close();
+        if (mFinishTransaction != null) mFinishTransaction.close();
         mStartTransaction = mFinishTransaction = null;
         if (mState < STATE_PLAYING) {
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -867,6 +890,7 @@
             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
                     false /* forceRelayout */);
         }
+        cleanUpInternal();
     }
 
     void abort() {
@@ -909,6 +933,7 @@
             dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
+            cleanUpInternal();
             return;
         }
 
@@ -1026,7 +1051,9 @@
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
                 mController.getTransitionPlayer().onTransitionReady(
-                        this, info, transaction, mFinishTransaction);
+                        mToken, info, transaction, mFinishTransaction);
+                // Since we created root-leash but no longer reference it from core, release it now
+                info.releaseAnimSurfaces();
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                     Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                             System.identityHashCode(this));
@@ -1059,7 +1086,17 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(this);
+        mController.finishTransition(mToken);
+    }
+
+    private void cleanUpInternal() {
+        // Clean-up any native references.
+        for (int i = 0; i < mChanges.size(); ++i) {
+            final ChangeInfo ci = mChanges.valueAt(i);
+            if (ci.mSnapshot != null) {
+                ci.mSnapshot.release();
+            }
+        }
     }
 
     /** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1815,10 +1852,6 @@
         return isCollecting() && mSyncId >= 0;
     }
 
-    static Transition fromBinder(IBinder binder) {
-        return (Transition) binder;
-    }
-
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
@@ -2325,4 +2358,18 @@
             }
         }
     }
+
+    private static class Token extends Binder {
+        final WeakReference<Transition> mTransition;
+
+        Token(Transition transition) {
+            mTransition = new WeakReference<>(transition);
+        }
+
+        @Override
+        public String toString() {
+            return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+                    + mTransition.get() + "}";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25df511..99527b1 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -458,8 +458,9 @@
                 info = new ActivityManager.RunningTaskInfo();
                 startTask.fillTaskInfo(info);
             }
-            mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
-                    transition.mType, info, remoteTransition, displayChange));
+            mTransitionPlayer.requestStartTransition(transition.getToken(),
+                    new TransitionRequestInfo(transition.mType, info, remoteTransition,
+                            displayChange));
             transition.setRemoteTransition(remoteTransition);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6032f87..f6f825f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3847,6 +3847,11 @@
                     || displayContent.isInTouchMode() == inTouch)) {
                 return;
             }
+            final boolean displayHasOwnTouchMode =
+                    displayContent != null && displayContent.hasOwnFocus();
+            if (displayHasOwnTouchMode && displayContent.isInTouchMode() == inTouch) {
+                return;
+            }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final boolean hasPermission =
@@ -3855,17 +3860,17 @@
                             /* printlog= */ false);
             final long token = Binder.clearCallingIdentity();
             try {
-                // If perDisplayFocusEnabled is set, then just update the display pointed by
-                // displayId
-                if (perDisplayFocusEnabled) {
+                // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+                // then just update the display pointed by displayId
+                if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
                     if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
                         displayContent.setInTouchMode(inTouch);
                     }
-                } else {  // Otherwise update all displays
+                } else {  // Otherwise update all displays that do not maintain their own touch mode
                     final int displayCount = mRoot.mChildren.size();
                     for (int i = 0; i < displayCount; ++i) {
                         DisplayContent dc = mRoot.mChildren.get(i);
-                        if (dc.isInTouchMode() == inTouch) {
+                        if (dc.isInTouchMode() == inTouch || dc.hasOwnFocus()) {
                             continue;
                         }
                         if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
@@ -8935,14 +8940,14 @@
     }
 
     @Override
-    public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+    public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
-                    Slog.e(TAG, "Unable to verify uid for package " + packageName
-                            + " for getPossibleMaximumWindowMetrics");
+                if (!mAtmService.isCallerRecents(callingUid)) {
+                    Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+                            + " on uid " + callingUid);
                     return new ArrayList<>();
                 }
 
@@ -8960,31 +8965,6 @@
         return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
     }
 
-    /**
-     * Returns {@code true} when the calling package is the recents component.
-     */
-    boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
-        String recentsPackage;
-        try {
-            String recentsComponent = mContext.getResources().getString(
-                    R.string.config_recentsComponentName);
-            if (recentsComponent == null) {
-                return false;
-            }
-            recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
-        } catch (Resources.NotFoundException e) {
-            Slog.e(TAG, "Unable to verify if recents component", e);
-            return false;
-        }
-        try {
-            return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
-                    && callingPackageName.equals(recentsPackage);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "Unable to verify if recents component", e);
-            return false;
-        }
-    }
-
     void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
         synchronized (mGlobalLock) {
             final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4c35178..aa1cf56 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -306,7 +306,7 @@
                                         nextTransition.setAllReady();
                                     }
                                 });
-                        return nextTransition;
+                        return nextTransition.getToken();
                     }
                     transition = mTransitionController.createTransition(type);
                 }
@@ -315,7 +315,7 @@
                 if (needsSetReady) {
                     transition.setAllReady();
                 }
-                return transition;
+                return transition.getToken();
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 63b7dfb..10d8b42 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -22,6 +22,7 @@
 #include <cutils/properties.h>
 #include <jni.h>
 #include <sensorservice/SensorService.h>
+#include <string.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
 
@@ -30,10 +31,14 @@
 #define PROXIMITY_ACTIVE_CLASS \
     "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
 
+#define RUNTIME_SENSOR_CALLBACK_CLASS \
+    "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+
 namespace android {
 
 static JavaVM* sJvm = nullptr;
 static jmethodID sMethodIdOnProximityActive;
+static jmethodID sMethodIdOnStateChanged;
 
 class NativeSensorService {
 public:
@@ -41,6 +46,11 @@
 
     void registerProximityActiveListener();
     void unregisterProximityActiveListener();
+    jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
+                               jobject callback);
+    void unregisterRuntimeSensor(jint handle);
+    jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
+                                    jfloatArray values);
 
 private:
     sp<SensorService> mService;
@@ -56,6 +66,18 @@
         jobject mListener;
     };
     sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+
+    class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+    public:
+        RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
+        ~RuntimeSensorCallbackDelegate();
+
+        void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+                            int64_t batchReportLatencyNs) override;
+
+    private:
+        jobject mCallback;
+    };
 };
 
 NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
@@ -85,6 +107,109 @@
     mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
 }
 
+jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
+                                                jstring vendor, jobject callback) {
+    if (mService == nullptr) {
+        ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
+        return -1;
+    }
+
+    sensor_t sensor{
+            .name = env->GetStringUTFChars(name, 0),
+            .vendor = env->GetStringUTFChars(vendor, 0),
+            .version = sizeof(sensor_t),
+            .type = type,
+    };
+
+    sp<RuntimeSensorCallbackDelegate> callbackDelegate(
+            new RuntimeSensorCallbackDelegate(env, callback));
+    return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate);
+}
+
+void NativeSensorService::unregisterRuntimeSensor(jint handle) {
+    if (mService == nullptr) {
+        ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+        return;
+    }
+
+    mService->unregisterRuntimeSensor(handle);
+}
+
+jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type,
+                                                     jlong timestamp, jfloatArray values) {
+    if (mService == nullptr) {
+        ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available.");
+        return false;
+    }
+    if (values == nullptr) {
+        ALOGD("Dropping sendRuntimeSensorEvent, no values.");
+        return false;
+    }
+
+    sensors_event_t event{
+            .version = sizeof(sensors_event_t),
+            .timestamp = timestamp,
+            .sensor = handle,
+            .type = type,
+    };
+
+    int valuesLength = env->GetArrayLength(values);
+    jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+
+    switch (type) {
+        case SENSOR_TYPE_ACCELEROMETER:
+        case SENSOR_TYPE_MAGNETIC_FIELD:
+        case SENSOR_TYPE_ORIENTATION:
+        case SENSOR_TYPE_GYROSCOPE:
+        case SENSOR_TYPE_GRAVITY:
+        case SENSOR_TYPE_LINEAR_ACCELERATION: {
+            if (valuesLength != 3) {
+                ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+                return false;
+            }
+            event.acceleration.x = sensorValues[0];
+            event.acceleration.y = sensorValues[1];
+            event.acceleration.z = sensorValues[2];
+            break;
+        }
+        case SENSOR_TYPE_DEVICE_ORIENTATION:
+        case SENSOR_TYPE_LIGHT:
+        case SENSOR_TYPE_PRESSURE:
+        case SENSOR_TYPE_TEMPERATURE:
+        case SENSOR_TYPE_PROXIMITY:
+        case SENSOR_TYPE_RELATIVE_HUMIDITY:
+        case SENSOR_TYPE_AMBIENT_TEMPERATURE:
+        case SENSOR_TYPE_SIGNIFICANT_MOTION:
+        case SENSOR_TYPE_STEP_DETECTOR:
+        case SENSOR_TYPE_TILT_DETECTOR:
+        case SENSOR_TYPE_WAKE_GESTURE:
+        case SENSOR_TYPE_GLANCE_GESTURE:
+        case SENSOR_TYPE_PICK_UP_GESTURE:
+        case SENSOR_TYPE_WRIST_TILT_GESTURE:
+        case SENSOR_TYPE_STATIONARY_DETECT:
+        case SENSOR_TYPE_MOTION_DETECT:
+        case SENSOR_TYPE_HEART_BEAT:
+        case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: {
+            if (valuesLength != 1) {
+                ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+                return false;
+            }
+            event.data[0] = sensorValues[0];
+            break;
+        }
+        default: {
+            if (valuesLength > 16) {
+                ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum.");
+                return false;
+            }
+            memcpy(event.data, sensorValues, valuesLength * sizeof(float));
+        }
+    }
+
+    status_t err = mService->sendRuntimeSensorEvent(event);
+    return err == OK;
+}
+
 NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
         JNIEnv* env, jobject listener)
       : mListener(env->NewGlobalRef(listener)) {}
@@ -98,6 +223,22 @@
     jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive));
 }
 
+NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env,
+                                                                                  jobject callback)
+      : mCallback(env->NewGlobalRef(callback)) {}
+
+NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() {
+    AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
+        bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
+                           static_cast<jint>(ns2us(samplingPeriodNs)),
+                           static_cast<jint>(ns2us(batchReportLatencyNs)));
+}
+
 static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
     NativeSensorService* service = new NativeSensorService(env, listener);
     return reinterpret_cast<jlong>(service);
@@ -113,26 +254,46 @@
     service->unregisterProximityActiveListener();
 }
 
-static const JNINativeMethod methods[] = {
-        {
-                "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
-                reinterpret_cast<void*>(startSensorServiceNative)
-        },
-        {
-                "registerProximityActiveListenerNative", "(J)V",
-                reinterpret_cast<void*>(registerProximityActiveListenerNative)
-        },
-        {
-                "unregisterProximityActiveListenerNative", "(J)V",
-                reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
-         },
+static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
+                                        jstring name, jstring vendor, jobject callback) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+}
 
+static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    service->unregisterRuntimeSensor(handle);
+}
+
+static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type,
+                                             jlong timestamp, jfloatArray values) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
+}
+
+static const JNINativeMethod methods[] = {
+        {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+         reinterpret_cast<void*>(startSensorServiceNative)},
+        {"registerProximityActiveListenerNative", "(J)V",
+         reinterpret_cast<void*>(registerProximityActiveListenerNative)},
+        {"unregisterProximityActiveListenerNative", "(J)V",
+         reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
+        {"registerRuntimeSensorNative",
+         "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+         reinterpret_cast<void*>(registerRuntimeSensorNative)},
+        {"unregisterRuntimeSensorNative", "(JI)V",
+         reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
+        {"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
+         reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
 };
 
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
     sJvm = vm;
     jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
     sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
+    jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
+    sMethodIdOnStateChanged =
+            GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
     return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
                                     NELEM(methods));
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 374da1c..d3b9e10 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -22,10 +22,11 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialOption;
 import android.credentials.GetCredentialRequest;
-import android.credentials.IClearCredentialSessionCallback;
+import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
@@ -34,6 +35,7 @@
 import android.os.ICancellationSignal;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.GetCredentialsRequest;
 import android.text.TextUtils;
 import android.util.Log;
@@ -198,7 +200,7 @@
             // Iterate over all provider sessions and invoke the request
             providerSessions.forEach(providerCreateSession -> {
                 providerCreateSession.getRemoteCredentialService().onCreateCredential(
-                        (android.service.credentials.CreateCredentialRequest)
+                        (BeginCreateCredentialRequest)
                                 providerCreateSession.getProviderRequest(),
                         /*callback=*/providerCreateSession);
             });
@@ -206,8 +208,8 @@
         }
 
         @Override
-        public ICancellationSignal clearCredentialSession(
-                IClearCredentialSessionCallback callback, String callingPackage) {
+        public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
+                IClearCredentialStateCallback callback, String callingPackage) {
             // TODO: implement.
             Log.i(TAG, "clearCredentialSession");
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 4cdc457..d0bc074 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -22,7 +22,7 @@
 import android.credentials.Credential;
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.service.credentials.CredentialProviderService;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
 
 /**
  * Helper class for setting up pending intent, and extracting objects from it.
@@ -37,14 +37,15 @@
         return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
     }
 
-    /** Extracts the {@link CredentialsDisplayContent} object added to the result data. */
-    public static CredentialsDisplayContent extractCredentialsDisplayContent(Intent resultData) {
+    /** Extracts the {@link CredentialsResponseContent} object added to the result data. */
+    public static CredentialsResponseContent extractResponseContent(Intent resultData) {
         if (resultData == null) {
             return null;
         }
         return resultData.getParcelableExtra(
-                CredentialProviderService.EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT,
-                CredentialsDisplayContent.class);
+                CredentialProviderService
+                        .EXTRA_GET_CREDENTIALS_CONTENT_RESULT,
+                CredentialsResponseContent.class);
     }
 
     /** Extracts the {@link CreateCredentialResponse} object added to the result data. */
@@ -53,7 +54,7 @@
             return null;
         }
         return resultData.getParcelableExtra(
-                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
+                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESULT,
                 CreateCredentialResponse.class);
     }
 
@@ -63,7 +64,7 @@
             return null;
         }
         return resultData.getParcelableExtra(
-                CredentialProviderService.EXTRA_GET_CREDENTIAL,
+                CredentialProviderService.EXTRA_CREDENTIAL_RESULT,
                 Credential.class);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 6bb8c60..332a75e 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -26,11 +26,12 @@
 import android.credentials.ui.Entry;
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.os.Bundle;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.CreateEntry;
 import android.service.credentials.CredentialProviderInfo;
 import android.service.credentials.CredentialProviderService;
-import android.service.credentials.SaveEntry;
 import android.util.Log;
 import android.util.Slog;
 
@@ -44,14 +45,14 @@
  * Will likely split this into remote response state and UI state.
  */
 public final class ProviderCreateSession extends ProviderSession<
-        CreateCredentialRequest, CreateCredentialResponse> {
+        BeginCreateCredentialRequest, BeginCreateCredentialResponse> {
     private static final String TAG = "ProviderCreateSession";
 
     // Key to be used as an entry key for a save entry
     private static final String SAVE_ENTRY_KEY = "save_entry_key";
 
     @NonNull
-    private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
+    private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
     /** The complete request to be used in the second round. */
     private final CreateCredentialRequest mCompleteRequest;
 
@@ -62,13 +63,19 @@
             CredentialProviderInfo providerInfo,
             CreateRequestSession createRequestSession,
             RemoteCredentialService remoteCredentialService) {
-        CreateCredentialRequest providerRequest =
+        CreateCredentialRequest providerCreateRequest =
                 createProviderRequest(providerInfo.getCapabilities(),
                         createRequestSession.mClientRequest,
                         createRequestSession.mClientCallingPackage);
-        if (providerRequest != null) {
+        if (providerCreateRequest != null) {
+            // TODO : Replace with proper splitting of request
+            BeginCreateCredentialRequest providerBeginCreateRequest =
+                    new BeginCreateCredentialRequest(
+                            providerCreateRequest.getCallingPackage(),
+                            providerCreateRequest.getType(),
+                            new Bundle());
             return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
-                    remoteCredentialService, providerRequest);
+                    remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
         }
         Log.i(TAG, "Unable to create provider session");
         return null;
@@ -87,36 +94,28 @@
         return null;
     }
 
-    private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
-        // TODO: Replace with first round bundle from request when ready
-        return new CreateCredentialRequest(
-                request.getCallingPackage(),
-                request.getType(),
-                new Bundle());
-    }
-
     private ProviderCreateSession(
             @NonNull Context context,
             @NonNull CredentialProviderInfo info,
             @NonNull ProviderInternalCallback callbacks,
             @UserIdInt int userId,
             @NonNull RemoteCredentialService remoteCredentialService,
-            @NonNull CreateCredentialRequest request) {
-        super(context, info, getFirstRoundRequest(request), callbacks, userId,
+            @NonNull BeginCreateCredentialRequest beginCreateRequest,
+            @NonNull CreateCredentialRequest completeCreateRequest) {
+        super(context, info, beginCreateRequest, callbacks, userId,
                 remoteCredentialService);
-        // TODO : Replace with proper splitting of request
-        mCompleteRequest = request;
+        mCompleteRequest = completeCreateRequest;
         setStatus(Status.PENDING);
     }
 
     /** Returns the save entry maintained in state by this provider session. */
-    public SaveEntry getUiSaveEntry(String entryId) {
+    public CreateEntry getUiSaveEntry(String entryId) {
         return mUiSaveEntries.get(entryId);
     }
 
     @Override
     public void onProviderResponseSuccess(
-            @Nullable CreateCredentialResponse response) {
+            @Nullable BeginCreateCredentialResponse response) {
         Log.i(TAG, "in onProviderResponseSuccess");
         onUpdateResponse(response);
     }
@@ -138,7 +137,7 @@
         }
     }
 
-    private void onUpdateResponse(CreateCredentialResponse response) {
+    private void onUpdateResponse(BeginCreateCredentialResponse response) {
         Log.i(TAG, "updateResponse with save entries");
         mProviderResponse = response;
         updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
@@ -152,15 +151,15 @@
             Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
             return null;
         }
-        final CreateCredentialResponse response = getProviderResponse();
+        final BeginCreateCredentialResponse response = getProviderResponse();
         if (response == null) {
             Log.i(TAG, "In prepareUiData response null");
             throw new IllegalStateException("Response must be in completion mode");
         }
-        if (response.getSaveEntries() != null) {
+        if (response.getCreateEntries() != null) {
             Log.i(TAG, "In prepareUiData save entries not null");
             return prepareUiProviderData(
-                    prepareUiSaveEntries(response.getSaveEntries()),
+                    prepareUiSaveEntries(response.getCreateEntries()),
                     null,
                     /*isDefaultProvider=*/false);
         }
@@ -192,24 +191,25 @@
         }
     }
 
-    private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+    private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
         Log.i(TAG, "in populateUiSaveEntries");
         List<Entry> uiSaveEntries = new ArrayList<>();
 
         // Populate the save entries
-        for (SaveEntry saveEntry : saveEntries) {
+        for (CreateEntry createEntry : saveEntries) {
             String entryId = generateEntryId();
-            mUiSaveEntries.put(entryId, saveEntry);
+            mUiSaveEntries.put(entryId, createEntry);
             Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
-            uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice(),
-                    saveEntry.getPendingIntent(), setUpFillInIntent(saveEntry.getPendingIntent())));
+            uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, createEntry.getSlice(),
+                    createEntry.getPendingIntent(), setUpFillInIntent(
+                            createEntry.getPendingIntent())));
         }
         return uiSaveEntries;
     }
 
     private Intent setUpFillInIntent(PendingIntent pendingIntent) {
         Intent intent = pendingIntent.getIntent();
-        intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+        intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
                 mCompleteRequest);
         return intent;
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index d63cdeb..6cd011b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -29,7 +29,7 @@
 import android.service.credentials.Action;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
 import android.service.credentials.GetCredentialsRequest;
 import android.service.credentials.GetCredentialsResponse;
 import android.util.Log;
@@ -211,20 +211,20 @@
                     prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
                     /*remoteEntry=*/null);
         }
-        if (mProviderResponse.getCredentialsDisplayContent() != null) {
-            Log.i(TAG, "In prepareUiData displayContent not null");
+        if (mProviderResponse.getCredentialsResponseContent() != null) {
+            Log.i(TAG, "In prepareUiData credentialsResponseContent not null");
             return prepareUiProviderData(prepareUiActionEntries(
-                            mProviderResponse.getCredentialsDisplayContent().getActions()),
-                    prepareUiCredentialEntries(mProviderResponse.getCredentialsDisplayContent()
+                            mProviderResponse.getCredentialsResponseContent().getActions()),
+                    prepareUiCredentialEntries(mProviderResponse.getCredentialsResponseContent()
                             .getCredentialEntries()),
                     /*authenticationAction=*/null,
                     prepareUiRemoteEntry(mProviderResponse
-                            .getCredentialsDisplayContent().getRemoteCredentialEntry()));
+                            .getCredentialsResponseContent().getRemoteCredentialEntry()));
         }
         return null;
     }
 
-    private Entry prepareUiRemoteEntry(Action remoteCredentialEntry) {
+    private Entry prepareUiRemoteEntry(CredentialEntry remoteCredentialEntry) {
         if (remoteCredentialEntry == null) {
             return null;
         }
@@ -316,11 +316,11 @@
             @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
         if (providerPendingIntentResponse != null) {
             if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
-                CredentialsDisplayContent content = PendingIntentResultHandler
-                        .extractCredentialsDisplayContent(providerPendingIntentResponse
+                CredentialsResponseContent content = PendingIntentResultHandler
+                        .extractResponseContent(providerPendingIntentResponse
                                 .getResultData());
                 if (content != null) {
-                    onUpdateResponse(GetCredentialsResponse.createWithDisplayContent(content));
+                    onUpdateResponse(GetCredentialsResponse.createWithResponseContent(content));
                     return;
                 }
             }
@@ -342,7 +342,7 @@
         if (response.getAuthenticationAction() != null) {
             Log.i(TAG , "updateResponse with authentication entry");
             updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
-        } else if (response.getCredentialsDisplayContent() != null) {
+        } else if (response.getCredentialsResponseContent() != null) {
             Log.i(TAG , "updateResponse with credentialEntries");
             // TODO validate response
             updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 4a07f0a..ac360bd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -23,7 +23,7 @@
 import android.credentials.Credential;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
-import android.service.credentials.Action;
+import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderException;
 import android.service.credentials.CredentialProviderInfo;
 import android.util.Pair;
@@ -50,7 +50,7 @@
     @Nullable protected Credential mFinalCredentialResponse;
     @NonNull protected final T mProviderRequest;
     @Nullable protected R mProviderResponse;
-    @Nullable protected Pair<String, Action> mUiRemoteEntry;
+    @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
 
     /**
      * Returns true if the given status reflects that the provider state is ready to be shown
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index c2464b5..e385bcb 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,14 +24,14 @@
 import android.os.Handler;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
-import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.CredentialProviderException;
 import android.service.credentials.CredentialProviderException.CredentialProviderError;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.GetCredentialsRequest;
 import android.service.credentials.GetCredentialsResponse;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
 import android.service.credentials.ICredentialProviderService;
 import android.service.credentials.IGetCredentialsCallback;
 import android.text.format.DateUtils;
@@ -146,27 +146,27 @@
                 handleExecutionResponse(result, error, cancellationSink, callback)));
     }
 
-    /** Main entry point to be called for executing a createCredential call on the remote
+    /** Main entry point to be called for executing a beginCreateCredential call on the remote
      * provider service.
      * @param request the request to be sent to the provider
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderCreateSession} class that maintains provider state
      */
-    public void onCreateCredential(@NonNull CreateCredentialRequest request,
-            ProviderCallbacks<CreateCredentialResponse> callback) {
+    public void onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+            ProviderCallbacks<BeginCreateCredentialResponse> callback) {
         Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
-        AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
+        AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
                 new AtomicReference<>();
 
-        CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
-            CompletableFuture<CreateCredentialResponse> createCredentialFuture =
+        CompletableFuture<BeginCreateCredentialResponse> connectThenExecute = postAsync(service -> {
+            CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
                     new CompletableFuture<>();
-            ICancellationSignal cancellationSignal = service.onCreateCredential(
-                    request, new ICreateCredentialCallback.Stub() {
+            ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+                    request, new IBeginCreateCredentialCallback.Stub() {
                         @Override
-                        public void onSuccess(CreateCredentialResponse response) {
-                            Log.i(TAG, "In onSuccess onCreateCredential "
+                        public void onSuccess(BeginCreateCredentialResponse response) {
+                            Log.i(TAG, "In onSuccess onBeginCreateCredential "
                                     + "in RemoteCredentialService");
                             createCredentialFuture.complete(response);
                         }
@@ -179,7 +179,7 @@
                             createCredentialFuture.completeExceptionally(
                                     new CredentialProviderException(errorCode, errorMsg));
                         }});
-            CompletableFuture<CreateCredentialResponse> future = futureRef.get();
+            CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
             if (future != null && future.isCancelled()) {
                 dispatchCancellationSignal(cancellationSignal);
             } else {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 8a8485a..9cb7533 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -16,24 +16,35 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Objects;
 
 final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
 
     @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value)
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value)
             throws IOException {
+        Objects.requireNonNull(value);
         serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
     }
 
+    @Nullable
     @Override
-    Boolean readFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException {
-        return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+    Boolean readFromXml(TypedXmlPullParser parser, String attributeName) {
+        try {
+            return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+            return null;
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 61d93c7..775e3d8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -81,6 +81,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT;
@@ -140,6 +141,7 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -309,6 +311,7 @@
 import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Telephony;
@@ -712,6 +715,17 @@
                     + "management app's authentication policy";
     private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
 
+    private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+    private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+
+    /**
+     * For apps targeting U+
+     * Enable multiple admins to coexist on the same device.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    static final long ENABLE_COEXISTENCE_CHANGE = 260560985L;
+
     final Context mContext;
     final Injector mInjector;
     final PolicyPathProvider mPathProvider;
@@ -795,6 +809,8 @@
     private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider;
     private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver;
 
+    private final DevicePolicyEngine mDevicePolicyEngine;
+
     private static final boolean ENABLE_LOCK_GUARD = true;
 
     /**
@@ -1864,6 +1880,8 @@
         mUserData = new SparseArray<>();
         mOwners = makeOwners(injector, pathProvider);
 
+        mDevicePolicyEngine = new DevicePolicyEngine(mContext);
+
         if (!mHasFeature) {
             // Skip the rest of the initialization
             mSetupContentObserver = null;
@@ -1908,6 +1926,9 @@
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
 
         mDeviceManagementResourcesProvider.load();
+        if (isCoexistenceFlagEnabled()) {
+            mDevicePolicyEngine.load();
+        }
 
         // The binder caches are not enabled until the first invalidation.
         invalidateBinderCaches();
@@ -7951,8 +7972,17 @@
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
                 || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
-        mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+        if (isCoexistenceEnabled(caller)) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUTO_TIMEZONE,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+                    enabled);
+        } else {
+            mInjector.binderWithCleanCallingIdentity(() ->
+                    mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+        }
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -12245,8 +12275,38 @@
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
-            final int userHandle = caller.getUserId();
-            setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+            if (packages.length == 0) {
+                mDevicePolicyEngine.removeLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        admin,
+                        caller.getUserId());
+            } else {
+                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+                LockTaskPolicy policy;
+                if (currentPolicy == null) {
+                    policy = new LockTaskPolicy(Set.of(packages));
+                } else {
+                    policy = currentPolicy.clone();
+                    policy.setPackages(Set.of(packages));
+                }
+
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+                        policy,
+                        caller.getUserId());
+            }
+        } else {
+            synchronized (getLockObject()) {
+                final int userHandle = caller.getUserId();
+                setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+            }
         }
     }
 
@@ -12267,8 +12327,21 @@
 
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
-            final List<String> packages = getUserData(userHandle).mLockTaskPackages;
-            return packages.toArray(new String[packages.size()]);
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            if (policy == null) {
+                return new String[0];
+            } else {
+                return policy.getPackages().toArray(new String[policy.getPackages().size()]);
+            }
+        } else {
+            synchronized (getLockObject()) {
+                final List<String> packages = getUserData(userHandle).mLockTaskPackages;
+                return packages.toArray(new String[packages.size()]);
+            }
         }
     }
 
@@ -12284,8 +12357,19 @@
         }
 
         final int userId = mInjector.userHandleGetCallingUserId();
-        synchronized (getLockObject()) {
-            return getUserData(userId).mLockTaskPackages.contains(pkg);
+        // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
+        //  could be an admin that hasn't targeted U.
+        if (isCoexistenceFlagEnabled()) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
+            if (policy == null) {
+                return false;
+            }
+            return policy.getPackages().contains(pkg);
+        } else {
+            synchronized (getLockObject()) {
+                return getUserData(userId).mLockTaskPackages.contains(pkg);
+            }
         }
     }
 
@@ -12308,7 +12392,28 @@
             enforceCanCallLockTaskLocked(caller);
             enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
-            setLockTaskFeaturesLocked(userHandle, flags);
+        }
+        if (isCoexistenceEnabled(caller)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK,
+                    caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+            if (currentPolicy == null) {
+                throw new IllegalArgumentException("Can't set a lock task flags without setting "
+                        + "lock task packages first.");
+            }
+            LockTaskPolicy policy = currentPolicy.clone();
+            policy.setFlags(flags);
+
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.LOCK_TASK,
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+                    policy,
+                    caller.getUserId());
+        } else {
+            synchronized (getLockObject()) {
+                setLockTaskFeaturesLocked(userHandle, flags);
+            }
         }
     }
 
@@ -12326,7 +12431,21 @@
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
-            return getUserData(userHandle).mLockTaskFeatures;
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            if (policy == null) {
+                // We default on the power button menu, in order to be consistent with pre-P
+                // behaviour.
+                return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+            }
+            return policy.getFlags();
+        } else {
+            synchronized (getLockObject()) {
+                return getUserData(userHandle).mLockTaskFeatures;
+            }
         }
     }
 
@@ -13905,6 +14024,20 @@
             if (isFinancedDeviceOwner(caller)) {
                 enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
             }
+        }
+        if (isCoexistenceEnabled(caller)) {
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+                    // TODO(b/260573124): Add correct enforcing admin when permission changes are
+                    //  merged, and don't forget to handle delegates! Enterprise admins assume
+                    //  component name isn't null.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+                    grantState,
+                    caller.getUserId());
+            // TODO: update javadoc to reflect that callback no longer return success/failure
+            callback.sendResult(Bundle.EMPTY);
+        } else {
+            synchronized (getLockObject()) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13921,14 +14054,16 @@
                     callback.sendResult(null);
                     return;
                 }
-                if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+                if (grantState == PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
                     AdminPermissionControlParams permissionParams =
-                            new AdminPermissionControlParams(packageName, permission, grantState,
+                            new AdminPermissionControlParams(packageName, permission,
+                                    grantState,
                                     canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
                     mInjector.getPermissionControllerManager(caller.getUserHandle())
-                            .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
+                            .setRuntimePermissionGrantStateByDeviceAdmin(
+                                    caller.getPackageName(),
                                     permissionParams, mContext.getMainExecutor(),
                                     (permissionWasSet) -> {
                                         if (isPostQAdmin && !permissionWasSet) {
@@ -13947,13 +14082,14 @@
 
                                         callback.sendResult(Bundle.EMPTY);
                                     });
-                }
-            } catch (SecurityException e) {
-                Slogf.e(LOG_TAG, "Could not set permission grant state", e);
+                    }
+                } catch (SecurityException e) {
+                    Slogf.e(LOG_TAG, "Could not set permission grant state", e);
 
-                callback.sendResult(null);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(ident);
+                    callback.sendResult(null);
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(ident);
+                }
             }
         }
     }
@@ -19017,4 +19153,18 @@
             return result;
         });
     }
+
+    // TODO(b/260560985): properly gate coexistence changes
+    private boolean isCoexistenceEnabled(CallerIdentity caller) {
+        return isCoexistenceFlagEnabled()
+                && mInjector.isChangeEnabled(
+                        ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId());
+    }
+
+    private boolean isCoexistenceFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                ENABLE_COEXISTENCE_FLAG,
+                DEFAULT_ENABLE_COEXISTENCE_FLAG);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index 3152f0b..d5949dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -16,24 +16,35 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Objects;
 
 final class IntegerPolicySerializer extends PolicySerializer<Integer> {
 
     @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value)
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value)
             throws IOException {
+        Objects.requireNonNull(value);
         serializer.attributeInt(/* namespace= */ null, attributeName, value);
     }
 
+    @Nullable
     @Override
-    Integer readFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException {
-        return parser.getAttributeInt(/* namespace= */ null, attributeName);
+    Integer readFromXml(TypedXmlPullParser parser, String attributeName) {
+        try {
+            return parser.getAttributeInt(/* namespace= */ null, attributeName);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+            return null;
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
index 9360fd7..d3e8de4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
@@ -16,7 +16,10 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -24,19 +27,16 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
 
 final class LockTaskPolicy {
-    private Set<String> mPackages;
-    private int mFlags;
+    static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+    private Set<String> mPackages = new HashSet<>();
+    private int mFlags = DEFAULT_LOCK_TASK_FLAG;
 
-    LockTaskPolicy(@Nullable Set<String> packages, int flags) {
-        mPackages = packages;
-        mFlags = flags;
-    }
-
-    @Nullable
+    @NonNull
     Set<String> getPackages() {
         return mPackages;
     }
@@ -45,8 +45,20 @@
         return mFlags;
     }
 
-    void setPackages(Set<String> packages) {
-        mPackages = packages;
+    LockTaskPolicy(Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages.addAll(packages);
+    }
+
+    private LockTaskPolicy(Set<String> packages, int flags) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
+        mFlags = flags;
+    }
+
+    void setPackages(@NonNull Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
     }
 
     void setFlags(int flags) {
@@ -54,6 +66,13 @@
     }
 
     @Override
+    public LockTaskPolicy clone() {
+        LockTaskPolicy policy = new LockTaskPolicy(mPackages);
+        policy.setFlags(mFlags);
+        return policy;
+    }
+
+    @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
@@ -67,6 +86,11 @@
         return Objects.hash(mPackages, mFlags);
     }
 
+    @Override
+    public String toString() {
+        return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags;
+    }
+
     static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
 
         private static final String ATTR_PACKAGES = ":packages";
@@ -74,15 +98,17 @@
         private static final String ATTR_FLAGS = ":flags";
 
         @Override
-        void saveToXml(
-                TypedXmlSerializer serializer, String attributeNamePrefix, LockTaskPolicy value)
-                throws IOException {
-            if (value.mPackages != null) {
-                serializer.attribute(
-                        /* namespace= */ null,
-                        attributeNamePrefix + ATTR_PACKAGES,
-                        String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
+        void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix,
+                @NonNull LockTaskPolicy value) throws IOException {
+            Objects.requireNonNull(value);
+            if (value.mPackages == null || value.mPackages.isEmpty()) {
+                throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
+                        + "packages must be present");
             }
+            serializer.attribute(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_PACKAGES,
+                    String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
             serializer.attributeInt(
                     /* namespace= */ null,
                     attributeNamePrefix + ATTR_FLAGS,
@@ -90,18 +116,24 @@
         }
 
         @Override
-        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix)
-                throws XmlPullParserException {
+        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
             String packagesStr = parser.getAttributeValue(
                     /* namespace= */ null,
                     attributeNamePrefix + ATTR_PACKAGES);
-            Set<String> packages = packagesStr == null
-                    ? null
-                    : Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
-            int flags = parser.getAttributeInt(
-                    /* namespace= */ null,
-                    attributeNamePrefix + ATTR_FLAGS);
-            return new LockTaskPolicy(packages, flags);
+            if (packagesStr == null) {
+                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+                return null;
+            }
+            Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+            try {
+                int flags = parser.getAttributeInt(
+                        /* namespace= */ null,
+                        attributeNamePrefix + ATTR_FLAGS);
+                return new LockTaskPolicy(packages, flags);
+            } catch (XmlPullParserException e) {
+                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+                return null;
+            }
         }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 3a18cb9..a787a0b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -25,8 +25,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -225,8 +223,8 @@
         mPolicySerializer.saveToXml(serializer, attributeName, value);
     }
 
-    V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException {
+    @Nullable
+    V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
         return mPolicySerializer.readFromXml(parser, attributeName);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index b645b97..74b6f9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -29,6 +29,7 @@
 
 import com.android.server.utils.Slogf;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
@@ -53,7 +54,7 @@
     static boolean setPermissionGrantState(
             @Nullable Integer grantState, @NonNull Context context, int userId,
             @NonNull String[] args) {
-        Binder.withCleanCallingIdentity(() -> {
+        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
             if (args == null || args.length < 2) {
                 throw new IllegalArgumentException("Package name and permission name must be "
                         + "provided as arguments");
@@ -84,8 +85,7 @@
                 // TODO: add logging
                 return false;
             }
-        });
-        return true;
+        }));
     }
 
     @NonNull
@@ -106,9 +106,14 @@
 
     static boolean setLockTask(
             @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
-        DevicePolicyManagerService.updateLockTaskPackagesLocked(
-                context, List.copyOf(policy.getPackages()), userId);
-        DevicePolicyManagerService.updateLockTaskFeaturesLocked(policy.getFlags(), userId);
+        List<String> packages = Collections.emptyList();
+        int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
+        if (policy != null) {
+            packages = List.copyOf(policy.getPackages());
+            flags = policy.getFlags();
+        }
+        DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
+        DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
         return true;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index b3259d3..528d3b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -16,16 +16,15 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
+
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 
 abstract class PolicySerializer<V> {
-    abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, V value)
+    abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value)
             throws IOException;
-    abstract V readFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException;
+    abstract V readFromXml(TypedXmlPullParser parser, String attributeName);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index aad82cd..d3dee98 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -40,7 +40,7 @@
     private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
 
     private final PolicyDefinition<V> mPolicyDefinition;
-    private final LinkedHashMap<EnforcingAdmin, V> mAdminsPolicy = new LinkedHashMap<>();
+    private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>();
     private V mCurrentResolvedPolicy;
 
     PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
@@ -49,13 +49,13 @@
 
     private PolicyState(
             @NonNull PolicyDefinition<V> policyDefinition,
-            @NonNull LinkedHashMap<EnforcingAdmin, V> adminsPolicy,
+            @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins,
             V currentEnforcedPolicy) {
         Objects.requireNonNull(policyDefinition);
-        Objects.requireNonNull(adminsPolicy);
+        Objects.requireNonNull(policiesSetByAdmins);
 
         mPolicyDefinition = policyDefinition;
-        mAdminsPolicy.putAll(adminsPolicy);
+        mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
         mCurrentResolvedPolicy = currentEnforcedPolicy;
     }
 
@@ -63,7 +63,7 @@
      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
      */
     boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
-        mAdminsPolicy.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
+        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
 
         return resolvePolicy();
     }
@@ -71,16 +71,20 @@
     boolean removePolicy(@NonNull EnforcingAdmin admin) {
         Objects.requireNonNull(admin);
 
-        if (mAdminsPolicy.remove(admin) == null) {
+        if (mPoliciesSetByAdmins.remove(admin) == null) {
             return false;
         }
 
         return resolvePolicy();
     }
 
+    LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+        return mPoliciesSetByAdmins;
+    }
+
     private boolean resolvePolicy() {
-        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mAdminsPolicy);
-        boolean policyChanged = Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
+        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
+        boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
         mCurrentResolvedPolicy = resolvedPolicy;
 
         return policyChanged;
@@ -94,14 +98,16 @@
     void saveToXml(TypedXmlSerializer serializer) throws IOException {
         mPolicyDefinition.saveToXml(serializer);
 
-        mPolicyDefinition.savePolicyValueToXml(
-                serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+        if (mCurrentResolvedPolicy != null) {
+            mPolicyDefinition.savePolicyValueToXml(
+                    serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+        }
 
-        for (EnforcingAdmin admin : mAdminsPolicy.keySet()) {
+        for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
             serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
 
             mPolicyDefinition.savePolicyValueToXml(
-                    serializer, ATTR_POLICY_VALUE, mAdminsPolicy.get(admin));
+                    serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin));
 
             serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
             admin.saveToXml(serializer);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 693f3a0..1bd5031 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -788,7 +788,7 @@
 
     private void updateDefaultSmsApp(@NonNull UserData userData) {
         ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser(
-                mContext, /* updateIfNeeded= */ false, userData.getUserId());
+                mContext, /* updateIfNeeded= */ false, UserHandle.of(userData.getUserId()));
         String defaultSmsApp = component != null ? component.getPackageName() : null;
         userData.setDefaultSmsApp(defaultSmsApp);
     }
diff --git a/services/proguard.flags b/services/proguard.flags
index 27fe505..6cdf11c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -88,6 +88,7 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 298cbf3..6af7269 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -74,6 +74,7 @@
 import android.app.Application;
 import android.app.IBackupAgent;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManager;
@@ -183,7 +184,7 @@
     private static final String BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS =
             "android.app.backup.BackupAgent$SharedPrefsSynchronizer";
     private static final int USER_ID = 10;
-    private static final int OPERATION_TYPE = BackupManager.OperationType.BACKUP;
+    private static final int BACKUP_DESTINATION = BackupAnnotations.BackupDestination.CLOUD;
 
     @Mock private TransportManager mTransportManager;
     @Mock private DataChangedJournal mOldJournal;
@@ -264,7 +265,8 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
         mBackupEligibilityRules = new BackupEligibilityRules(mPackageManager,
-                LocalServices.getService(PackageManagerInternal.class), USER_ID, OPERATION_TYPE);
+                LocalServices.getService(PackageManagerInternal.class), USER_ID,
+                BACKUP_DESTINATION);
     }
 
     @After
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 12e7cfc..212ec14 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -18,6 +18,11 @@
           package="com.android.frameworks.inputmethodtests">
 
     <uses-sdk android:targetSdkVersion="31" />
+    <queries>
+        <intent>
+            <action android:name="android.view.InputMethod" />
+        </intent>
+    </queries>
 
     <!-- Permissions required for granting and logging -->
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
@@ -29,9 +34,23 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
+
     <application android:testOnly="true"
                  android:debuggable="true">
         <uses-library android:name="android.test.runner" />
+        <service android:name="com.android.server.inputmethod.InputMethodBindingControllerTest$EmptyInputMethodService"
+                 android:label="Empty IME"
+                 android:permission="android.permission.BIND_INPUT_METHOD"
+                 android:process=":service"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.view.InputMethod"/>
+            </intent-filter>
+            <meta-data android:name="android.view.im"
+                       android:resource="@xml/method"/>
+        </service>
     </application>
 
     <instrumentation
diff --git a/services/tests/InputMethodSystemServerTests/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
new file mode 100644
index 0000000..89b06bb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
@@ -0,0 +1,18 @@
+<?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.
+  -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
new file mode 100644
index 0000000..42d373b
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.inputmethodservice.InputMethodService;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.inputmethod.InputBindResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodBindingControllerTest extends InputMethodManagerServiceTestBase {
+
+    private static final String PACKAGE_NAME = "com.android.frameworks.inputmethodtests";
+    private static final String TEST_SERVICE_NAME =
+            "com.android.server.inputmethod.InputMethodBindingControllerTest"
+                    + "$EmptyInputMethodService";
+    private static final String TEST_IME_ID = PACKAGE_NAME + "/" + TEST_SERVICE_NAME;
+    private static final long TIMEOUT_IN_SECONDS = 3;
+
+    private InputMethodBindingController mBindingController;
+    private Instrumentation mInstrumentation;
+    private final int mImeConnectionBindFlags =
+            InputMethodBindingController.IME_CONNECTION_BIND_FLAGS
+                    & ~Context.BIND_SCHEDULE_LIKE_TOP_APP;
+    private CountDownLatch mCountDownLatch;
+
+    public static class EmptyInputMethodService extends InputMethodService {}
+
+    @Before
+    public void setUp() throws RemoteException {
+        super.setUp();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mCountDownLatch = new CountDownLatch(1);
+        // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
+        // from system.
+        mBindingController =
+                new InputMethodBindingController(
+                        mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+    }
+
+    @Test
+    public void testBindCurrentMethod_noIme() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(null);
+            InputBindResult result = mBindingController.bindCurrentMethod();
+            assertThat(result).isEqualTo(InputBindResult.NO_IME);
+        }
+    }
+
+    @Test
+    public void testBindCurrentMethod_unknownId() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId("unknown ime id");
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            synchronized (ImfLock.class) {
+                mBindingController.bindCurrentMethod();
+            }
+        });
+    }
+
+    @Test
+    public void testBindCurrentMethod_notConnected() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(TEST_IME_ID);
+            doReturn(false)
+                    .when(mContext)
+                    .bindServiceAsUser(
+                            any(Intent.class),
+                            any(ServiceConnection.class),
+                            anyInt(),
+                            any(UserHandle.class));
+
+            InputBindResult result = mBindingController.bindCurrentMethod();
+            assertThat(result).isEqualTo(InputBindResult.IME_NOT_CONNECTED);
+        }
+    }
+
+    @Test
+    public void testBindAndUnbindMethod() throws Exception {
+        // Bind with main connection
+        testBindCurrentMethodWithMainConnection();
+
+        // Bind with visible connection
+        testBindCurrentMethodWithVisibleConnection();
+
+        // Unbind both main and visible connections
+        testUnbindCurrentMethod();
+    }
+
+    private void testBindCurrentMethodWithMainConnection() throws Exception {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(TEST_IME_ID);
+        }
+        InputMethodInfo info = mInputMethodManagerService.mMethodMap.get(TEST_IME_ID);
+        assertThat(info).isNotNull();
+        assertThat(info.getId()).isEqualTo(TEST_IME_ID);
+        assertThat(info.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+
+        // Bind input method with main connection. It is called on another thread because we should
+        // wait for onServiceConnected() to finish.
+        InputBindResult result = callOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                return mBindingController.bindCurrentMethod();
+            }
+        });
+
+        verify(mContext, times(1))
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        any(ServiceConnection.class),
+                        eq(mImeConnectionBindFlags),
+                        any(UserHandle.class));
+        assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
+        assertThat(result.id).isEqualTo(info.getId());
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.hasConnection()).isTrue();
+            assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
+            assertThat(mBindingController.getCurToken()).isNotNull();
+        }
+        // Wait for onServiceConnected()
+        mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+        // Verify onServiceConnected() is called and bound successfully.
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.getCurMethod()).isNotNull();
+            assertThat(mBindingController.getCurMethodUid()).isNotEqualTo(Process.INVALID_UID);
+        }
+    }
+
+    private void testBindCurrentMethodWithVisibleConnection() {
+        mInstrumentation.runOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                mBindingController.setCurrentMethodVisible();
+            }
+        });
+        // Bind input method with visible connection
+        verify(mContext, times(1))
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        any(ServiceConnection.class),
+                        eq(InputMethodBindingController.IME_VISIBLE_BIND_FLAGS),
+                        any(UserHandle.class));
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.isVisibleBound()).isTrue();
+        }
+    }
+
+    private void testUnbindCurrentMethod() {
+        mInstrumentation.runOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                mBindingController.unbindCurrentMethod();
+            }
+        });
+
+        synchronized (ImfLock.class) {
+            // Unbind both main connection and visible connection
+            assertThat(mBindingController.hasConnection()).isFalse();
+            assertThat(mBindingController.isVisibleBound()).isFalse();
+            verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
+            assertThat(mBindingController.getCurToken()).isNull();
+            assertThat(mBindingController.getCurId()).isNull();
+            assertThat(mBindingController.getCurMethod()).isNull();
+            assertThat(mBindingController.getCurMethodUid()).isEqualTo(Process.INVALID_UID);
+        }
+    }
+
+    private static <V> V callOnMainSync(Callable<V> callable) {
+        AtomicReference<V> result = new AtomicReference<>();
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            try {
+                                result.set(callable.call());
+                            } catch (Exception e) {
+                                throw new RuntimeException("Exception was thrown", e);
+                            }
+                        });
+        return result.get();
+    }
+}
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 1f66a11..7bf9a9e 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
@@ -17,7 +17,12 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.Intent
-import android.content.pm.*
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
 import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
@@ -27,16 +32,32 @@
 import com.android.internal.R
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.*
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
+import com.android.server.pm.pkg.component.ParsedAttributionImpl
+import com.android.server.pm.pkg.component.ParsedComponentImpl
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.server.pm.pkg.component.ParsedProcessImpl
+import com.android.server.pm.pkg.component.ParsedProviderImpl
+import com.android.server.pm.pkg.component.ParsedServiceImpl
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import java.security.KeyPairGenerator
 import java.security.PublicKey
+import java.util.UUID
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
 class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
 
+    companion object {
+        private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
+    }
+
     override val defaultImpl = PackageImpl.forTesting("com.example.test")
     override val creator = PackageImpl.CREATOR
 
@@ -72,8 +93,6 @@
         "getLongVersionCode",
         // Tested through constructor
         "getManifestPackageName",
-        // Utility methods
-        "getStorageUuid",
         // Removal not tested, irrelevant for parcelling concerns
         "removeUsesOptionalLibrary",
         "clearAdoptPermissions",
@@ -85,6 +104,7 @@
         // Tested manually
         "getMimeGroups",
         "getRequestedPermissions",
+        "getStorageUuid",
         // Tested through asSplit
         "asSplit",
         "getSplits",
@@ -155,7 +175,6 @@
         AndroidPackage::getResizeableActivity,
         AndroidPackage::getRestrictedAccountType,
         AndroidPackage::getRoundIconRes,
-        PackageImpl::getSeInfo,
         PackageImpl::getSecondaryCpuAbi,
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
@@ -241,7 +260,7 @@
     )
 
     override fun extraParams() = listOf(
-        getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+        getter(AndroidPackage::getVolumeUuid, TEST_UUID.toString()),
         getter(AndroidPackage::isProfileable, true),
         getter(PackageImpl::getVersionCode, 3),
         getter(PackageImpl::getVersionCodeMajor, 9),
@@ -602,6 +621,8 @@
         expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1)
         expect.that(after.usesStaticLibrariesCertDigests!![0]).asList()
                 .containsExactly("testCertDigest2")
+
+        expect.that(after.storageUuid).isEqualTo(TEST_UUID)
     }
 
     private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 51fa851..e7c384b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -252,6 +252,7 @@
         noteBoot(4);
         assertTrue(RescueParty.isRebootPropertySet());
 
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
         noteBoot(5);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
@@ -276,6 +277,7 @@
         noteAppCrash(4, true);
         assertTrue(RescueParty.isRebootPropertySet());
 
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
         noteAppCrash(5, true);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
@@ -429,6 +431,27 @@
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
         }
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        noteBoot(LEVEL_FACTORY_RESET + 1);
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isFactoryResetPropertySet());
+    }
+
+    @Test
+    public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() {
+        for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i + 1);
+        }
+        int mitigationCount = LEVEL_FACTORY_RESET + 1;
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        noteBoot(mitigationCount + 1);
         assertTrue(RescueParty.isAttemptingFactoryReset());
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 66e7ec0..c87fd26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -49,6 +49,7 @@
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.appwidget.AppWidgetManager;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -538,6 +539,59 @@
     }
 
     /**
+     * Verify that we don't let urgent broadcasts starve delivery of non-urgent
+     */
+    @Test
+    public void testUrgentStarvation() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        // mix of broadcasts, with more than 2 fg/urgent
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+                        optInteractive), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+                        optInteractive), 0);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+        // verify the reset-count-then-resume worked too
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 55d1160..9234431 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -439,14 +439,25 @@
     @SuppressWarnings("GuardedBy")
     @Test
     public void testUpdateOomAdj_DoOne_FgService_ShortFgs() {
+        sService.mConstants.TOP_TO_FGS_GRACE_DURATION = 100_000;
+        sService.mConstants.mShortFgsProcStateExtraWaitDuration = 200_000;
+
+        ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.startRequested = true;
+        s.isForeground = true;
+        s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+        s.setShortFgsInfo(SystemClock.uptimeMillis());
+
         // SHORT_SERVICE FGS will get IMP_FG and a slightly different recent-adjustment.
         {
             ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                     MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+            app.mServices.startService(s);
             app.mServices.setHasForegroundServices(true,
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
             app.mState.setLastTopTime(SystemClock.uptimeMillis());
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
             assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
@@ -461,9 +472,11 @@
                     MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
             app.mServices.setHasForegroundServices(true,
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+            app.mServices.startService(s);
             app.mState.setLastTopTime(SystemClock.uptimeMillis()
                     - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
             assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
@@ -471,6 +484,33 @@
             // Still should get network access.
             assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) != 0);
         }
+
+        // SHORT_SERVICE, timed out already.
+        s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.startRequested = true;
+        s.isForeground = true;
+        s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+        s.setShortFgsInfo(SystemClock.uptimeMillis()
+                - sService.mConstants.mShortFgsTimeoutDuration
+                - sService.mConstants.mShortFgsProcStateExtraWaitDuration);
+        {
+            ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                    MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+            app.mServices.setHasForegroundServices(true,
+                    ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+            app.mServices.startService(s);
+            app.mState.setLastTopTime(SystemClock.uptimeMillis()
+                    - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
+            sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+            // Procstate should be lower than FGS. (It should be SERVICE)
+            assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
+
+            // Shouldn't have the network capability now.
+            assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) == 0);
+        }
     }
 
     @SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index dc77762..8d2e1ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,6 +17,7 @@
 package com.android.server.app;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
 import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -1454,7 +1455,58 @@
         verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
     }
 
-    private void setGameState(boolean isLoading) {
+    @Test
+    public void testSetGameStateLoading_withNoDeviceConfig() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE);
+        int testMode = GameState.MODE_GAMEPLAY_INTERRUPTIBLE;
+        int testLabel = 99;
+        int testQuality = 123;
+        GameState gameState = new GameState(true, testMode, testLabel, testQuality);
+        assertEquals(testMode, gameState.getMode());
+        assertEquals(testLabel, gameState.getLabel());
+        assertEquals(testQuality, gameState.getQuality());
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+        mTestLooper.moveTimeForward(GameManagerService.LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+    }
+
+    @Test
+    public void testSetGameStateLoading_withDeviceConfig() {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        GameState gameState = new GameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE, 99, 123);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+        reset(mMockPowerManager);
+        assertTrue(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(2000);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+    }
+
+    @Test
+    public void testSetGameStateNotLoading() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
         GameManagerService gameManagerService =
@@ -1462,27 +1514,19 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.setGameMode(
                 mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        int testMode = GameState.MODE_NONE;
+        int testMode = GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE;
         int testLabel = 99;
         int testQuality = 123;
-        GameState gameState = new GameState(isLoading, testMode, testLabel, testQuality);
-        assertEquals(isLoading, gameState.isLoading());
+        GameState gameState = new GameState(false, testMode, testLabel, testQuality);
+        assertFalse(gameState.isLoading());
         assertEquals(testMode, gameState.getMode());
         assertEquals(testLabel, gameState.getLabel());
         assertEquals(testQuality, gameState.getQuality());
         gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
         mTestLooper.dispatchAll();
-        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
-    }
-
-    @Test
-    public void testSetGameStateLoading() {
-        setGameState(true);
-    }
-
-    @Test
-    public void testSetGameStateNotLoading() {
-        setGameState(false);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        assertFalse(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
     }
 
     private List<String> readGameModeInterventionList() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 5dc1251..be13bad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@
     StaticMockitoSession mSession;
 
     @Mock
-    AppOpsService.Constants mConstants;
+    AppOpsServiceImpl.Constants mConstants;
 
     @Mock
     Context mContext;
@@ -57,7 +57,7 @@
     Handler mHandler;
 
     @Mock
-    AppOpsServiceInterface mLegacyAppOpsService;
+    AppOpsCheckingServiceInterface mLegacyAppOpsService;
 
     AppOpsRestrictions mAppOpsRestrictions;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index c0688d1..7d4bc6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,6 +22,8 @@
 import static android.app.AppOpsManager.OP_READ_SMS;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.app.AppOpsManager.resolvePackageName;
+import static android.os.Process.INVALID_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,6 +41,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 
+import android.app.AppOpsManager;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.PackageOps;
 import android.content.ContentResolver;
@@ -86,13 +89,13 @@
 
     private File mAppOpsFile;
     private Handler mHandler;
-    private AppOpsService mAppOpsService;
+    private AppOpsServiceImpl mAppOpsService;
     private int mMyUid;
     private long mTestStartMillis;
     private StaticMockitoSession mMockingSession;
 
     private void setupAppOpsService() {
-        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
+        mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
 
         // Always approve all permission checks
@@ -161,17 +164,20 @@
 
     @Test
     public void testNoteOperationAndGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
 
         // Note an op that's allowed.
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
 
         // Note another op that's not allowed.
-        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
-                false);
+        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -185,18 +191,20 @@
     @Test
     public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
         // This op controls WIFI_SCAN
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
 
-        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
-                null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
+        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
 
         // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
-        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
-                null, false).getOpMode()).isEqualTo(MODE_ERRORED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -205,11 +213,14 @@
     // Tests the dumping and restoring of the in-memory state to/from XML.
     @Test
     public void testStatePersistence() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
-        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
-                false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         mAppOpsService.writeState();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -224,8 +235,10 @@
     // Tests that ops are persisted during shutdown.
     @Test
     public void testShutdown() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         mAppOpsService.shutdown();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -238,8 +251,10 @@
 
     @Test
     public void testGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         // Query all ops
         List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -267,8 +282,10 @@
 
     @Test
     public void testPackageRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -322,8 +339,10 @@
 
     @Test
     public void testUidRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 98e895a..3efd5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -76,7 +76,7 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    AppOpsService.Constants mConstants;
+    AppOpsServiceImpl.Constants mConstants;
 
     AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index e08a715..298dbf4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -93,12 +93,13 @@
         }
     }
 
-    private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) {
+    private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+            int op1, int op2) {
         int numberOfNonDefaultOps = 0;
         final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
         for(int i = 0; i < uidStates.size(); i++) {
-            final AppOpsService.UidState uidState = uidStates.valueAt(i);
+            final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
             SparseIntArray opModes = uidState.getNonDefaultUidModes();
             if (opModes != null) {
                 final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -112,12 +113,12 @@
                 continue;
             }
             for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
+                final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
                 if (ops == null) {
                     continue;
                 }
-                final AppOpsService.Op _op1 = ops.get(op1);
-                final AppOpsService.Op _op2 = ops.get(op2);
+                final AppOpsServiceImpl.Op _op1 = ops.get(op1);
+                final AppOpsServiceImpl.Op _op2 = ops.get(op2);
                 final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
                 final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
                 assertEquals(mode1, mode2);
@@ -158,8 +159,8 @@
         // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
         when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
 
-        AppOpsService testService = spy(
-                new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade
+        AppOpsServiceImpl testService = spy(
+                new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
         assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
                 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
         mHandler.removeCallbacks(testService.mWriteRunner);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
index ac23d4e..014ef3d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
@@ -34,14 +34,16 @@
     public static final int DEFAULT_USERID = 0;
 
     private final IntArray mRunningUserIds;
+    private final IntArray mVisibleUserIds;
     private final SparseArray<IntArray> mProfiles;
 
     private int mCurrentUserId;
 
     public FakeUserInfoHelper() {
         mCurrentUserId = DEFAULT_USERID;
-        mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID});
+        mRunningUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
         mProfiles = new SparseArray<>();
+        mVisibleUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
     }
 
     public void startUser(int userId) {
@@ -65,6 +67,7 @@
             mRunningUserIds.remove(idx);
         }
 
+        setUserInvisibleInternal(userId);
         dispatchOnUserStopped(userId);
     }
 
@@ -82,16 +85,39 @@
         // ensure all profiles are started if they didn't exist before...
         for (int userId : currentProfileUserIds) {
             startUserInternal(userId, false);
+            setUserVisibleInternal(userId, true);
         }
 
         if (oldUserId != mCurrentUserId) {
             dispatchOnCurrentUserChanged(oldUserId, mCurrentUserId);
+            setUserVisibleInternal(mCurrentUserId, true);
         }
     }
 
-    @Override
-    public int[] getRunningUserIds() {
-        return mRunningUserIds.toArray();
+    private void setUserVisibleInternal(int userId, boolean alwaysDispatch) {
+        int idx = mVisibleUserIds.indexOf(userId);
+        if (idx < 0) {
+            mVisibleUserIds.add(userId);
+        } else if (!alwaysDispatch) {
+            return;
+        }
+        dispatchOnVisibleUserChanged(userId, true);
+    }
+
+    private void setUserInvisibleInternal(int userId) {
+        int idx = mVisibleUserIds.indexOf(userId);
+        if (idx >= 0) {
+            mVisibleUserIds.remove(userId);
+        }
+        dispatchOnVisibleUserChanged(userId, false);
+    }
+
+    public void setUserVisible(int userId, boolean visible) {
+        if (visible) {
+            setUserVisibleInternal(userId, true);
+        } else {
+            setUserInvisibleInternal(userId);
+        }
     }
 
     @Override
@@ -100,11 +126,21 @@
     }
 
     @Override
+    public int[] getRunningUserIds() {
+        return mRunningUserIds.toArray();
+    }
+
+    @Override
     public int getCurrentUserId() {
         return mCurrentUserId;
     }
 
     @Override
+    public boolean isVisibleUserId(int userId) {
+        return mVisibleUserIds.indexOf(userId) >= 0;
+    }
+
+    @Override
     protected int[] getProfileIds(int userId) {
         IntArray profiles = mProfiles.get(userId);
         if (profiles != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
index 490b2e8..d9aa232 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.location.injector;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -31,6 +32,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.location.injector.UserInfoHelper.UserListener;
+import com.android.server.pm.UserManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,13 +47,14 @@
 
     private static final int USER1_ID = 1;
     private static final int USER1_MANAGED_ID = 11;
-    private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+    private static final int[] USER1_PROFILES = new int[] {USER1_ID, USER1_MANAGED_ID};
     private static final int USER2_ID = 2;
     private static final int USER2_MANAGED_ID = 12;
-    private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+    private static final int[] USER2_PROFILES = new int[] {USER2_ID, USER2_MANAGED_ID};
 
     @Mock private Context mContext;
     @Mock private UserManager mUserManager;
+    @Mock private UserManagerInternal mUserManagerInternal;
 
     private SystemUserInfoHelper mHelper;
 
@@ -63,12 +66,15 @@
         doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID);
         doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID);
 
+        LocalServices.addService(UserManagerInternal.class, mUserManagerInternal);
+
         mHelper = new SystemUserInfoHelper(mContext);
     }
 
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
     }
 
     @Test
@@ -77,11 +83,11 @@
         mHelper.addListener(listener);
 
         mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID);
-        verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
-        verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID,
+        verify(listener).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener).onUserChanged(USER1_MANAGED_ID,
                 UserListener.CURRENT_USER_CHANGED);
-        verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
-        verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID,
+        verify(listener).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener).onUserChanged(USER2_MANAGED_ID,
                 UserListener.CURRENT_USER_CHANGED);
 
         mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID);
@@ -94,6 +100,25 @@
     }
 
     @Test
+    public void testListener_UserVisibilityChanged() {
+        mHelper.onSystemReady();
+        verify(mUserManagerInternal).addUserVisibilityListener(any());
+
+        UserListener listener = mock(UserListener.class);
+        mHelper.addListener(listener);
+
+        mHelper.dispatchOnVisibleUserChanged(USER1_ID, false);
+        mHelper.dispatchOnVisibleUserChanged(USER2_ID, true);
+        verify(listener).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
+        verify(listener).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
+
+        mHelper.dispatchOnVisibleUserChanged(USER2_ID, false);
+        mHelper.dispatchOnVisibleUserChanged(USER1_ID, true);
+        verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
+        verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
+    }
+
+    @Test
     public void testListener_StartUser() {
         UserListener listener = mock(UserListener.class);
         mHelper.addListener(listener);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 20e4e80..aa28ad4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -175,7 +175,9 @@
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
 
         mInjector = new TestInjector(mContext);
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
         mInjector.getUserInfoHelper().startUser(OTHER_USER);
+        mInjector.getUserInfoHelper().setUserVisible(OTHER_USER, true);
 
         mPassive = new PassiveLocationProviderManager(mContext, mInjector);
         mPassive.startManager(null);
@@ -331,6 +333,20 @@
     }
 
     @Test
+    public void testGetLastLocation_InvisibleUser() {
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+        assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isNull();
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+        assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isEqualTo(loc);
+    }
+
+    @Test
     public void testGetLastLocation_Bypass() {
         mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
                 new PackageTagsList.Builder().add(
@@ -569,6 +585,25 @@
     }
 
     @Test
+    public void testRegisterListener_InvisibleUser() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0)
+                .setWorkSource(WORK_SOURCE)
+                .build();
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+        mProvider.setProviderLocation(createLocationResult(NAME, mRandom));
+        verify(listener, never()).onLocationChanged(any(List.class),
+                nullable(IRemoteCallback.class));
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+        LocationResult loc = createLocationResult(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
+    }
+
+    @Test
     public void testRegisterListener_ExpiringAlarm() throws Exception {
         ILocationListener listener = createMockLocationListener();
         LocationRequest request = new LocationRequest.Builder(0)
@@ -799,6 +834,17 @@
     }
 
     @Test
+    public void testGetCurrentLocation_InvisibleUser() throws Exception {
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
+
+        verify(listener).onLocation(isNull());
+    }
+
+    @Test
     public void testFlush() throws Exception {
         ILocationListener listener = createMockLocationListener();
         mManager.registerLocationRequest(
@@ -1008,6 +1054,21 @@
     }
 
     @Test
+    public void testProviderRequest_InvisibleUser() {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(5)
+                .setWorkSource(WORK_SOURCE)
+                .build();
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+        assertThat(mProvider.getRequest().isActive()).isTrue();
+    }
+
+    @Test
     public void testProviderRequest_IgnoreLocationSettings() {
         mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
                 new PackageTagsList.Builder().add(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
index afcedd6..a97491d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -40,7 +40,6 @@
     private static final String TAG = AsyncUserVisibilityListener.class.getSimpleName();
 
     private static final long WAIT_TIMEOUT_MS = 2_000;
-
     private static final long WAIT_NO_EVENTS_TIMEOUT_MS = 100;
 
     private static int sNextId;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 1be7e2e..01674bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED;
+import static com.android.server.pm.BackgroundDexOptService.STATUS_FATAL_ERROR;
 import static com.android.server.pm.BackgroundDexOptService.STATUS_OK;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -24,7 +25,9 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -105,12 +108,16 @@
     @Mock
     private BackgroundDexOptJobService mJobServiceForIdle;
 
-    private final JobParameters mJobParametersForPostBoot = new JobParameters(null,
-            BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null,
-            0, false, false, null, null, null);
-    private final JobParameters mJobParametersForIdle = new JobParameters(null,
-            BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null,
-            0, false, false, null, null, null);
+    private final JobParameters mJobParametersForPostBoot =
+            createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
+    private final JobParameters mJobParametersForIdle =
+            createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
+
+    private static JobParameters createJobParameters(int jobId) {
+        JobParameters params = mock(JobParameters.class);
+        when(params.getJobId()).thenReturn(jobId);
+        return params;
+    }
 
     private BackgroundDexOptService mService;
 
@@ -264,6 +271,20 @@
     }
 
     @Test
+    public void testIdleJobFullRunWithFatalError() {
+        initUntilBootCompleted();
+        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
+                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+
+        doThrow(RuntimeException.class).when(mDexOptHelper).performDexOptWithStatus(any());
+
+        runFullJob(mJobServiceForIdle, mJobParametersForIdle,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_FATAL_ERROR,
+                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+    }
+
+    @Test
     public void testSystemReadyWhenDisabled() {
         when(mInjector.isBackgroundDexOptDisabled()).thenReturn(true);
 
@@ -510,13 +531,21 @@
         ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
         verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture());
 
-        argThreadRunnable.getValue().run();
+        try {
+            argThreadRunnable.getValue().run();
+        } catch (RuntimeException e) {
+            if (expectedStatus != STATUS_FATAL_ERROR) {
+                throw e;
+            }
+        }
 
         verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params,
                 expectedReschedule);
         // Never block
         verify(mDexOptHelper, never()).controlDexOptBlocking(true);
-        verifyPerformDexOpt();
+        if (expectedStatus != STATUS_FATAL_ERROR) {
+            verifyPerformDexOpt();
+        }
         assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index dd6c733..27d0662 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -139,7 +139,7 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(SystemProperties::class.java)
                 .mockStatic(SystemConfig::class.java)
-                .mockStatic(SELinuxMMAC::class.java)
+                .mockStatic(SELinuxMMAC::class.java, Mockito.CALLS_REAL_METHODS)
                 .mockStatic(FallbackCategoryProvider::class.java)
                 .mockStatic(PackageManagerServiceUtils::class.java)
                 .mockStatic(Environment::class.java)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index c5a8572..6d8910e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -15,12 +15,14 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserHandle.USER_NULL;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
 import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
 import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
 
@@ -40,6 +42,88 @@
     }
 
     @Test
+    public void testStartFgUser_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testSwitchFgUser_onDefaultDisplay() throws Exception {
+        int previousCurrentUserId = OTHER_USER_ID;
+        int currentUserId = USER_ID;
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(previousCurrentUserId),
+                onInvisible(previousCurrentUserId),
+                onVisible(currentUserId));
+        startForegroundUser(previousCurrentUserId);
+
+        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(currentUserId);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(currentUserId);
+
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+        expectUserIsNotVisibleAtAll(previousCurrentUserId);
+        expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public void testStartFgUser_onInvalidDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -89,15 +173,15 @@
         startDefaultProfile();
 
         // Make sure they were visible before
-        expectUserIsVisibleOnDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
-        expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index fc0287f..1065392 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,7 +15,15 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
 
 import org.junit.Test;
 
@@ -33,6 +41,88 @@
     }
 
     @Test
+    public void testStartFgUser_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testSwitchFgUser_onDefaultDisplay() throws Exception {
+        int previousCurrentUserId = OTHER_USER_ID;
+        int currentUserId = USER_ID;
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(previousCurrentUserId),
+                onInvisible(previousCurrentUserId),
+                onVisible(currentUserId));
+        startForegroundUser(previousCurrentUserId);
+
+        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(currentUserId);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(currentUserId);
+
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+        expectUserIsNotVisibleAtAll(previousCurrentUserId);
+        expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public void testStartBgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 6ceb38a..c203831 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -37,6 +37,7 @@
 
 import android.annotation.UserIdInt;
 import android.os.Handler;
+import android.text.TextUtils;
 import android.util.IntArray;
 import android.util.Log;
 
@@ -135,66 +136,6 @@
     }
 
     @Test
-    public final void testStartFgUser_onDefaultDisplay() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(USER_ID));
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(USER_ID);
-        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
-        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
-        // TODO(b/244644281): once isUserVisible() is fixed (see note there), this assertion will
-        // fail on MUMD, so we'll need to refactor / split this test (and possibly others)
-        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-        expectVisibleUsers(USER_ID);
-
-        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
-        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
-        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
-
-        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
-
-        listener.verify();
-    }
-
-    @Test
-    public final void testSwitchFgUser_onDefaultDisplay() throws Exception {
-        int previousCurrentUserId = OTHER_USER_ID;
-        int currentUserId = USER_ID;
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(previousCurrentUserId),
-                onInvisible(previousCurrentUserId),
-                onVisible(currentUserId));
-        startForegroundUser(previousCurrentUserId);
-
-        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(currentUserId);
-        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
-        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
-        expectUserIsVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
-        expectVisibleUsers(currentUserId);
-
-        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
-        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
-        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
-
-        expectUserIsNotVisibleAtAll(previousCurrentUserId);
-        expectNoDisplayAssignedToUser(previousCurrentUserId);
-
-        listener.verify();
-    }
-
-    @Test
     public final void testStartFgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -245,31 +186,6 @@
     }
 
     @Test
-    public final void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(PARENT_USER_ID),
-                onVisible(PROFILE_USER_ID));
-        startForegroundUser(PARENT_USER_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(PROFILE_USER_ID);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
-        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
-        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
     public final void testStopVisibleProfile() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(
                 onInvisible(INITIAL_CURRENT_USER_ID),
@@ -530,6 +446,14 @@
                 .isFalse();
     }
 
+    protected void expectUserIsNotVisibleOnDisplay(String when, @UserIdInt int userId,
+            int displayId) {
+        String suffix = TextUtils.isEmpty(when) ? "" : " on " + when;
+        expectWithMessage("mediator.isUserVisible(%s, %s)%s", userId, displayId, suffix)
+                .that(mMediator.isUserVisible(userId, displayId))
+                .isFalse();
+    }
+
     protected void expectUserIsNotVisibleAtAll(@UserIdInt int userId) {
         expectWithMessage("mediator.isUserVisible(%s)", userId)
                 .that(mMediator.isUserVisible(userId))
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index d477cb6..799a7fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -108,7 +109,7 @@
     @Before
     public void setUp() {
         final InternalResourceService irs = mock(InternalResourceService.class);
-        when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+        when(irs.isVip(anyInt(), anyString(), anyLong())).thenReturn(false);
         mEconomicPolicy = new MockEconomicPolicy(irs);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index ddfa05c..c46ebf2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -402,6 +402,6 @@
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
         pkgInfo.applicationInfo = applicationInfo;
-        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(pkgInfo));
+        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo));
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index ae25c1b..cb59d37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -44,7 +44,7 @@
 
     private MockitoSession mSession;
 
-    private final Exception mException = new Exception("D'OH!");
+    private final Throwable mThrowable = new Throwable("D'OH!");
 
     @Before
     public void setup() {
@@ -78,10 +78,10 @@
     }
 
     @Test
-    public void testV_msgAndException() {
-        Slogf.v(TAG, "msg", mException);
+    public void testV_msgAndThrowable() {
+        Slogf.v(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.v(TAG, "msg", mException));
+        verify(()-> Slog.v(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -103,12 +103,12 @@
     }
 
     @Test
-    public void testV_msgFormattedWithException_enabled() {
+    public void testV_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.VERBOSE);
 
-        Slogf.v(TAG, mException, "msg in a %s", "bottle");
+        Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.v(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
@@ -128,10 +128,10 @@
     }
 
     @Test
-    public void testD_msgAndException() {
-        Slogf.d(TAG, "msg", mException);
+    public void testD_msgAndThrowable() {
+        Slogf.d(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.d(TAG, "msg", mException));
+        verify(()-> Slog.d(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -153,19 +153,19 @@
     }
 
     @Test
-    public void testD_msgFormattedWithException_enabled() {
+    public void testD_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.DEBUG);
 
-        Slogf.d(TAG, mException, "msg in a %s", "bottle");
+        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.d(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testD_msgFormattedWithException_disabled() {
         disableLogging(Log.DEBUG);
 
-        Slogf.d(TAG, mException, "msg in a %s", "bottle");
+        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -178,10 +178,10 @@
     }
 
     @Test
-    public void testI_msgAndException() {
-        Slogf.i(TAG, "msg", mException);
+    public void testI_msgAndThrowable() {
+        Slogf.i(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.i(TAG, "msg", mException));
+        verify(()-> Slog.i(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -203,19 +203,19 @@
     }
 
     @Test
-    public void testI_msgFormattedWithException_enabled() {
+    public void testI_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.INFO);
 
-        Slogf.i(TAG, mException, "msg in a %s", "bottle");
+        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.i(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testI_msgFormattedWithException_disabled() {
         disableLogging(Log.INFO);
 
-        Slogf.i(TAG, mException, "msg in a %s", "bottle");
+        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -228,17 +228,17 @@
     }
 
     @Test
-    public void testW_msgAndException() {
-        Slogf.w(TAG, "msg", mException);
+    public void testW_msgAndThrowable() {
+        Slogf.w(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.w(TAG, "msg", mException));
+        verify(()-> Slog.w(TAG, "msg", mThrowable));
     }
 
     @Test
-    public void testW_exception() {
-        Slogf.w(TAG, mException);
+    public void testW_Throwable() {
+        Slogf.w(TAG, mThrowable);
 
-        verify(()-> Slog.w(TAG, mException));
+        verify(()-> Slog.w(TAG, mThrowable));
     }
 
     @Test
@@ -260,19 +260,19 @@
     }
 
     @Test
-    public void testW_msgFormattedWithException_enabled() {
+    public void testW_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.WARN);
 
-        Slogf.w(TAG, mException, "msg in a %s", "bottle");
+        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.w(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testW_msgFormattedWithException_disabled() {
         disableLogging(Log.WARN);
 
-        Slogf.w(TAG, mException, "msg in a %s", "bottle");
+        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -285,10 +285,10 @@
     }
 
     @Test
-    public void testE_msgAndException() {
-        Slogf.e(TAG, "msg", mException);
+    public void testE_msgAndThrowable() {
+        Slogf.e(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.e(TAG, "msg", mException));
+        verify(()-> Slog.e(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -310,19 +310,19 @@
     }
 
     @Test
-    public void testE_msgFormattedWithException_enabled() {
+    public void testE_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.ERROR);
 
-        Slogf.e(TAG, mException, "msg in a %s", "bottle");
+        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.e(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testE_msgFormattedWithException_disabled() {
         disableLogging(Log.ERROR);
 
-        Slogf.e(TAG, mException, "msg in a %s", "bottle");
+        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -335,17 +335,17 @@
     }
 
     @Test
-    public void testWtf_msgAndException() {
-        Slogf.wtf(TAG, "msg", mException);
+    public void testWtf_msgAndThrowable() {
+        Slogf.wtf(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.wtf(TAG, "msg", mException));
+        verify(()-> Slog.wtf(TAG, "msg", mThrowable));
     }
 
     @Test
-    public void testWtf_exception() {
-        Slogf.wtf(TAG, mException);
+    public void testWtf_Throwable() {
+        Slogf.wtf(TAG, mThrowable);
 
-        verify(()-> Slog.wtf(TAG, mException));
+        verify(()-> Slog.wtf(TAG, mThrowable));
     }
 
     @Test
@@ -377,10 +377,10 @@
     }
 
     @Test
-    public void testWtf_msgFormattedWithException() {
-        Slogf.wtf(TAG, mException, "msg in a %s", "bottle");
+    public void testWtf_msgFormattedWithThrowable() {
+        Slogf.wtf(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.wtf(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
     }
 
     private void enableLogging(@Log.Level int level) {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index fbd1293..1c43097 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -54,7 +54,6 @@
         "hamcrest-library",
         "servicestests-utils",
         "service-jobscheduler",
-        "service-permission.impl",
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 0f09252..52a550b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -58,7 +58,6 @@
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityWindowAttributes;
 
 import androidx.test.InstrumentationRegistry;
@@ -106,8 +105,6 @@
             LABEL,
             DESCRIPTION,
             TEST_PENDING_INTENT);
-    private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
-            new AccessibilityAction(ACTION_ID, LABEL);
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
 
@@ -282,10 +279,12 @@
     @Test
     public void testRegisterProxy() throws Exception {
         mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
-        verify(mProxyManager).registerProxy(mMockServiceClient, TEST_DISPLAY);
+        verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
+                eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
+                eq(mA11yms), eq(mA11yms.getTraceManager()),
+                eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
     }
 
-
     @SmallTest
     @Test
     public void testRegisterProxyWithoutPermission() throws Exception {
@@ -296,7 +295,8 @@
             Assert.fail();
         } catch (SecurityException expected) {
         }
-        verify(mProxyManager, never()).registerProxy(mMockServiceClient, TEST_DISPLAY);
+        verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+                any(), any(), any(), any());
     }
 
     @SmallTest
@@ -307,7 +307,8 @@
             Assert.fail();
         } catch (IllegalArgumentException expected) {
         }
-        verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.DEFAULT_DISPLAY);
+        verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+                any(), any(), any(), any());
     }
 
     @SmallTest
@@ -318,7 +319,30 @@
             Assert.fail();
         } catch (IllegalArgumentException expected) {
         }
-        verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.INVALID_DISPLAY);
+        verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+                any(), any(), any(), any());
+    }
+
+    @SmallTest
+    @Test
+    public void testUnRegisterProxyWithPermission() throws Exception {
+        mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
+        mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+
+        verify(mProxyManager).unregisterProxy(TEST_DISPLAY);
+    }
+
+    @SmallTest
+    @Test
+    public void testUnRegisterProxyWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy)
+                .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+        try {
+            mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+            Assert.fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY);
     }
 
     @SmallTest
@@ -417,6 +441,8 @@
     @SmallTest
     @Test
     public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
         final AccessibilityUserState userState = mA11yms.mUserStates.get(
                 mA11yms.getCurrentUserIdLocked());
         userState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
@@ -432,6 +458,8 @@
     @SmallTest
     @Test
     public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
         setupAccessibilityServiceConnection(0);
         when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
 
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 6016558..cd2f205 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -29,7 +29,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 import android.content.Context;
@@ -148,18 +148,18 @@
     }
 
     @Test
-    public void testGetOperationTypeFromTransport_returnsBackupByDefault()
+    public void testGetBackupDestinationFromTransport_returnsCloudByDefault()
             throws Exception {
         when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport);
         when(mBackupTransport.getTransportFlags()).thenReturn(0);
 
-        int operationType = mService.getOperationTypeFromTransport(mTransportConnection);
+        int backupDestination = mService.getBackupDestinationFromTransport(mTransportConnection);
 
-        assertThat(operationType).isEqualTo(OperationType.BACKUP);
+        assertThat(backupDestination).isEqualTo(BackupDestination.CLOUD);
     }
 
     @Test
-    public void testGetOperationTypeFromTransport_returnsMigrationForMigrationTransport()
+    public void testGetBackupDestinationFromTransport_returnsDeviceTransferForD2dTransport()
             throws Exception {
         // This is a temporary flag to control the new behaviour until it's ready to be fully
         // rolled out.
@@ -169,9 +169,9 @@
         when(mBackupTransport.getTransportFlags()).thenReturn(
                 BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER);
 
-        int operationType = mService.getOperationTypeFromTransport(mTransportConnection);
+        int backupDestination = mService.getBackupDestinationFromTransport(mTransportConnection);
 
-        assertThat(operationType).isEqualTo(OperationType.MIGRATION);
+        assertThat(backupDestination).isEqualTo(BackupDestination.DEVICE_TRANSFER);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
index 310c8f4..48b0aad 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
@@ -23,7 +23,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -78,7 +78,7 @@
         MockitoAnnotations.initMocks(this);
 
         mUserId = UserHandle.USER_SYSTEM;
-        mBackupEligibilityRules = getBackupEligibilityRules(OperationType.BACKUP);
+        mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
     }
 
     @Test
@@ -225,7 +225,7 @@
                 /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
 
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.MIGRATION);
+                BackupDestination.DEVICE_TRANSFER);
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
         assertThat(isEligible).isTrue();
@@ -237,7 +237,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.SYSTEM_UID,
                 ApplicationInfo.FLAG_SYSTEM, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.MIGRATION);
+                BackupDestination.DEVICE_TRANSFER);
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
         assertThat(isEligible).isFalse();
@@ -250,7 +250,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
         when(mPackageManager.getPropertyAsUser(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP),
                 eq(TEST_PACKAGE_NAME), isNull(), eq(mUserId)))
                 .thenReturn(getAdbBackupProperty(/* allowAdbBackup */ false));
@@ -267,7 +267,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
         when(mPackageManager.getPropertyAsUser(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP),
                 eq(TEST_PACKAGE_NAME), isNull(), eq(mUserId)))
                 .thenReturn(getAdbBackupProperty(/* allowAdbBackup */ true));
@@ -284,7 +284,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ ApplicationInfo.FLAG_DEBUGGABLE, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
 
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
@@ -298,7 +298,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 ApplicationInfo.FLAG_ALLOW_BACKUP, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
 
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
@@ -312,7 +312,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
 
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
@@ -787,9 +787,10 @@
         assertThat(result).isFalse();
     }
 
-    private BackupEligibilityRules getBackupEligibilityRules(@OperationType int operationType) {
+    private BackupEligibilityRules getBackupEligibilityRules(
+            @BackupDestination int backupDestination) {
         return new BackupEligibilityRules(mPackageManager, mMockPackageManagerInternal, mUserId,
-                operationType);
+                backupDestination);
     }
 
     private static Signature generateSignature(byte i) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
new file mode 100644
index 0000000..ef8a49f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.Sensor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class SensorControllerTest {
+
+    private static final int VIRTUAL_DEVICE_ID = 42;
+    private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
+    private static final int SENSOR_HANDLE = 7;
+
+    @Mock
+    private SensorManagerInternal mSensorManagerInternalMock;
+    private SensorController mSensorController;
+    private VirtualSensorEvent mSensorEvent;
+    private VirtualSensorConfig mVirtualSensorConfig;
+    private IBinder mSensorToken;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+        mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
+        mVirtualSensorConfig =
+                new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+                        .build();
+        mSensorToken = new Binder("sensorToken");
+    }
+
+    @Test
+    public void createSensor_invalidHandle_throwsException() {
+        doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
+                anyInt(), anyInt(), anyString(), anyString(), any());
+
+        Throwable thrown = assertThrows(
+                RuntimeException.class,
+                () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+
+        assertThat(thrown.getCause().getMessage())
+                .contains("Received an invalid virtual sensor handle");
+    }
+
+    @Test
+    public void createSensor_success() {
+        doCreateSensorSuccessfully();
+
+        assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+    }
+
+    @Test
+    public void sendSensorEvent_invalidToken_throwsException() {
+        doCreateSensorSuccessfully();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mSensorController.sendSensorEvent(
+                        new Binder("invalidSensorToken"), mSensorEvent));
+    }
+
+    @Test
+    public void sendSensorEvent_success() {
+        doCreateSensorSuccessfully();
+
+        mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+        verify(mSensorManagerInternalMock).sendSensorEvent(
+                SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
+                mSensorEvent.getValues());
+    }
+
+    @Test
+    public void unregisterSensor_invalidToken_throwsException() {
+        doCreateSensorSuccessfully();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
+    }
+
+    @Test
+    public void unregisterSensor_success() {
+        doCreateSensorSuccessfully();
+
+        mSensorController.unregisterSensor(mSensorToken);
+        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+    }
+
+    private void doCreateSensorSuccessfully() {
+        doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+                anyInt(), anyInt(), anyString(), anyString(), any());
+        mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 09dc367..afaee04 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -58,6 +59,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
+import android.hardware.Sensor;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
 import android.hardware.input.VirtualKeyEvent;
@@ -88,6 +90,7 @@
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -99,6 +102,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -125,16 +129,19 @@
     private static final int VENDOR_ID = 5;
     private static final String UNIQUE_ID = "uniqueid";
     private static final String PHYS = "phys";
-    private static final int DEVICE_ID = 42;
+    private static final int DEVICE_ID = 53;
     private static final int HEIGHT = 1800;
     private static final int WIDTH = 900;
+    private static final int SENSOR_HANDLE = 64;
     private static final Binder BINDER = new Binder("binder");
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+    private static final int VIRTUAL_DEVICE_ID =  42;
 
     private Context mContext;
     private InputManagerMockHelper mInputManagerMockHelper;
     private VirtualDeviceImpl mDeviceImpl;
     private InputController mInputController;
+    private SensorController mSensorController;
     private AssociationInfo mAssociationInfo;
     private VirtualDeviceManagerService mVdms;
     private VirtualDeviceManagerInternal mLocalService;
@@ -149,6 +156,8 @@
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
+    private SensorManagerInternal mSensorManagerInternalMock;
+    @Mock
     private IVirtualDeviceActivityListener mActivityListener;
     @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -181,14 +190,36 @@
         return blockedActivities;
     }
 
+    private Intent createRestrictedActivityBlockedIntent(List displayCategories,
+            String targetDisplayCategory) {
+        mDeviceImpl.onVirtualDisplayCreatedLocked(
+                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+                DISPLAY_ID);
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoveDevices= */ true,
+                targetDisplayCategory);
+        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+                activityInfos.get(0), mAssociationInfo.getDisplayName());
+        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        return blockedAppIntent;
+    }
+
+
     private ArrayList<ActivityInfo> getActivityInfoList(
-            String packageName, String name, boolean displayOnRemoveDevices) {
+            String packageName, String name, boolean displayOnRemoveDevices,
+            String targetDisplayCategory) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.packageName = packageName;
         activityInfo.name = name;
         activityInfo.flags = displayOnRemoveDevices
                 ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
         activityInfo.applicationInfo = mApplicationInfoMock;
+        activityInfo.targetDisplayCategory = targetDisplayCategory;
         return new ArrayList<>(Arrays.asList(activityInfo));
     }
 
@@ -205,6 +236,9 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
         final DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.uniqueId = UNIQUE_ID;
         doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
@@ -229,6 +263,7 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mContext.getSystemService(WindowManager.class), threadVerifier);
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
 
         mAssociationInfo = new AssociationInfo(1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -241,9 +276,9 @@
                 .setBlockedActivities(getBlockedActivities())
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
-                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
-                mActivityListener, mRunningAppsChangedCallback, params);
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mInputController, mSensorController, (int associationId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
     }
 
@@ -285,9 +320,9 @@
                 .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
-                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
-                mActivityListener, mRunningAppsChangedCallback, params);
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mInputController, mSensorController, (int associationId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
 
         assertThat(
@@ -298,7 +333,7 @@
     @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         // This call should not throw any exceptions.
         mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
     }
@@ -317,7 +352,7 @@
     public void onVirtualDisplayRemovedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
 
         mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID);
         TestableLooper.get(this).processAllMessages();
@@ -379,7 +414,7 @@
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), anyInt(), eq(null));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), eq(DISPLAY_ID), eq(null));
@@ -388,9 +423,10 @@
     @Test
     public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
             throws RemoteException {
-        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController();
+        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
+                new ArrayList<>());
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         assertThrows(IllegalStateException.class,
                 () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID));
         TestableLooper.get(this).processAllMessages();
@@ -409,7 +445,7 @@
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -425,7 +461,7 @@
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -552,6 +588,18 @@
     }
 
     @Test
+    public void createVirtualSensor_noPermission_failsSecurityException() {
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(
+                SecurityException.class,
+                () -> mDeviceImpl.createVirtualSensor(
+                        BINDER,
+                        new VirtualSensorConfig.Builder(
+                                Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+    }
+
+    @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -625,7 +673,7 @@
     @Test
     public void onAudioSessionStarting_hasVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
 
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
 
@@ -635,7 +683,7 @@
     @Test
     public void onAudioSessionEnded_noVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.onAudioSessionEnded();
@@ -646,7 +694,7 @@
     @Test
     public void close_cleanVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.close();
@@ -655,6 +703,17 @@
     }
 
     @Test
+    public void close_cleanSensorController() {
+        mSensorController.addSensorForTesting(
+                BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME);
+
+        mDeviceImpl.close();
+
+        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+    }
+
+    @Test
     public void sendKeyEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
@@ -863,14 +922,16 @@
     @Test
     public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
-                NONBLOCKED_APP_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -882,14 +943,16 @@
     @Test
     public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
-                PERMISSION_CONTROLLER_PACKAGE_NAME, /* displayOnRemoveDevices */  false);
+                PERMISSION_CONTROLLER_PACKAGE_NAME,
+                /* displayOnRemoveDevices */  false,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -901,14 +964,16 @@
     @Test
     public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 SETTINGS_PACKAGE_NAME,
-                SETTINGS_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                SETTINGS_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -920,14 +985,16 @@
     @Test
     public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 VENDING_PACKAGE_NAME,
-                VENDING_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                VENDING_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -939,14 +1006,16 @@
     @Test
     public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 GOOGLE_DIALER_PACKAGE_NAME,
-                GOOGLE_DIALER_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                GOOGLE_DIALER_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -958,14 +1027,16 @@
     @Test
     public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 GOOGLE_MAPS_PACKAGE_NAME,
-                GOOGLE_MAPS_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                GOOGLE_MAPS_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -978,7 +1049,7 @@
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
 
@@ -993,7 +1064,7 @@
     public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
@@ -1003,4 +1074,37 @@
 
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
     }
+
+    @Test
+    public void nonRestrictedActivityOnRestrictedVirtualDisplay_startBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"),
+                /* targetDisplayCategory= */ null);
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+
+    }
+
+    @Test
+    public void restrictedActivityOnRestrictedVirtualDisplay_doesNotStartBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "abc");
+        verify(mContext, never()).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void restrictedActivityOnNonRestrictedVirtualDisplay_startBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(
+                /* displayCategories= */ List.of(), "abc");
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void
+            restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 036b6df..a226ebc 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -16,9 +16,14 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.os.Parcel;
 import android.os.UserHandle;
 
@@ -27,18 +32,25 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class VirtualDeviceParamsTest {
 
+    private static final String SENSOR_NAME = "VirtualSensorName";
+    private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
     @Test
     public void parcelable_shouldRecreateSuccessfully() {
         VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
                 .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
                 .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
-                .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
-                        VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
+                .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+                .addVirtualSensorConfig(
+                        new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                                .setVendor(SENSOR_VENDOR)
+                                .build())
                 .build();
         Parcel parcel = Parcel.obtain();
         originalParams.writeToParcel(parcel, 0);
@@ -49,7 +61,14 @@
         assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
         assertThat(params.getUsersWithMatchingAccounts())
                 .containsExactly(UserHandle.of(123), UserHandle.of(456));
-        assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
-                .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
+        assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+
+        List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
+        assertThat(sensorConfigs).hasSize(1);
+        VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
+        assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
+        assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
+        assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
+        assertThat(sensorConfig.getStateChangeCallback()).isNull();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 0262f56..3ca648c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -72,22 +72,25 @@
         MockitoAnnotations.initMocks(this);
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         mVirtualAudioController = new VirtualAudioController(mContext);
-        mGenericWindowPolicyController = new GenericWindowPolicyController(
-                FLAG_SECURE,
-                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
-                /* allowedUsers= */ new ArraySet<>(),
-                /* allowedCrossTaskNavigations= */ new ArraySet<>(),
-                /* blockedCrossTaskNavigations= */ new ArraySet<>(),
-                /* allowedActivities= */ new ArraySet<>(),
-                /* blockedActivities= */ new ArraySet<>(),
-                VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
-                /* activityListener= */ null,
-                /* pipBlockedCallback= */ null,
-                /* activityBlockedCallback= */ null,
-                /* secureWindowCallback= */ null,
-                /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
+        mGenericWindowPolicyController =
+                new GenericWindowPolicyController(
+                        FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        /* allowedUsers= */ new ArraySet<>(),
+                        /* allowedCrossTaskNavigations= */ new ArraySet<>(),
+                        /* blockedCrossTaskNavigations= */ new ArraySet<>(),
+                        /* allowedActivities= */ new ArraySet<>(),
+                        /* blockedActivities= */ new ArraySet<>(),
+                        VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
+                        /* activityListener= */ null,
+                        /* pipBlockedCallback= */ null,
+                        /* activityBlockedCallback= */ null,
+                        /* secureWindowCallback= */ null,
+                        /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING,
+                        /* displayCategories= */ new ArrayList<>());
     }
 
+
     @Test
     public void startListening_receivesCallback() throws RemoteException {
         ArraySet<Integer> runningUids = new ArraySet<>();
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 062bde8..ce35626 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -171,22 +171,6 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
-    private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return true;
-                }
-            };
-
-    private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return false;
-                }
-            };
-
     @Mock InputManagerInternal mMockInputManagerInternal;
     @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -408,6 +392,75 @@
         assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
     }
 
+    @Test
+    public void testCreateVirtualDisplayOwnFocus() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+    }
+
     /**
      * Tests that the virtual display is created along-side the default display.
      */
@@ -1044,13 +1097,32 @@
     }
 
     /**
-     * Tests that the frame rate override is updated accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
      */
     @Test
     public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
-        testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
     }
 
     /**
@@ -1078,10 +1150,7 @@
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/ false);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  false);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
     }
 
     /**
@@ -1090,10 +1159,7 @@
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
     }
 
     /**
@@ -1316,10 +1382,9 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(
-            DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
+                new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
         registerDefaultDisplays(displayManager);
@@ -1341,40 +1406,12 @@
         Display.Mode expectedMode;
         if (compatChangeEnabled) {
             expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else if (injector.getAllowNonNativeRefreshRateOverride()) {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
         } else {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
         }
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverride(
-            DisplayManagerService.Injector injector) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
-        assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
-    }
-
     private int getDisplayIdForDisplayDevice(
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 59c69d1..a5d7a10 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -33,6 +33,7 @@
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
 import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,6 +54,8 @@
     @Mock
     private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
     @Mock
+    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+    @Mock
     private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
     @Mock
     private Context mContext;
@@ -65,6 +68,7 @@
     public void before() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getResources()).thenReturn(mResources);
+        when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
         DisplayBrightnessStrategySelector.Injector injector =
                 new DisplayBrightnessStrategySelector.Injector() {
                     @Override
@@ -83,6 +87,11 @@
                     }
 
                     @Override
+                    TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+                        return mTemporaryBrightnessStrategy;
+                    }
+
+                    @Override
                     InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
                         return mInvalidBrightnessStrategy;
                     }
@@ -121,10 +130,21 @@
     }
 
     @Test
+    public void selectStrategySelectsTemporaryStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mTemporaryBrightnessStrategy);
+    }
+
+    @Test
     public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
                 DisplayManagerInternal.DisplayPowerRequest.class);
         displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
                 Display.STATE_ON), mInvalidBrightnessStrategy);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
new file mode 100644
index 0000000..4a32796
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class TemporaryBrightnessStrategyTest {
+    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mTemporaryBrightnessStrategy = new TemporaryBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpectedWhenTemporaryBrightnessIsSet() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float temporaryBrightness = 0.2f;
+        mTemporaryBrightnessStrategy.setTemporaryScreenBrightness(temporaryBrightness);
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_TEMPORARY);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(temporaryBrightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(temporaryBrightness)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mTemporaryBrightnessStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+        assertEquals(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness(),
+                Float.NaN, 0.0f);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 5b11466..09cd47a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -143,8 +143,12 @@
                 Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
 
         assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+        mTestLooper.dispatchAll();
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+        mTestLooper.moveTimeForward(ArcTerminationActionFromAvr.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 7df0078..6a899e8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -102,7 +102,7 @@
                         Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index ac57834..0419768 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -111,7 +111,7 @@
                         Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index 5722ff3..167e0f8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -80,6 +80,17 @@
                 R.bool.config_cecRoutingControlDisabled_default);
 
         doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarMode_userConfigurable);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeEnabled_allowed);
+        doReturn(false).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeEnabled_default);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeDisabled_allowed);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeDisabled_default);
+
+        doReturn(true).when(resources).getBoolean(
                 R.bool.config_cecPowerControlMode_userConfigurable);
         doReturn(true).when(resources).getBoolean(
                 R.bool.config_cecPowerControlModeTv_allowed);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index 392d7f1..870b681 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -76,6 +76,7 @@
                 .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                     HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                     HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+                    HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -116,6 +117,7 @@
                 .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                     HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                     HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+                    HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -157,6 +159,7 @@
                 .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+                    HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 08d0e90..7c6c990 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -354,7 +354,7 @@
         HdmiCecMessage expectedMessage =
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
-        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
         assertThat(mMusicMute).isTrue();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 75c4d92..3ed8983 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -31,6 +31,7 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
@@ -45,6 +46,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -85,9 +88,13 @@
     private boolean mActiveMediaSessionsPaused;
     private FakePowerManagerInternalWrapper mPowerManagerInternal =
             new FakePowerManagerInternalWrapper();
+    @Mock
+    protected AudioManager mAudioManager;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
 
@@ -103,12 +110,17 @@
                     }
 
                     @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
                     void pauseActiveMediaSessions() {
                         mActiveMediaSessionsPaused = true;
                     }
 
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
@@ -1424,6 +1436,32 @@
     }
 
     @Test
+    public void sendVolumeKeyEvent_toLocalDevice_discardMessage() {
+        HdmiCecLocalDeviceAudioSystem audioSystem =
+                new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+        audioSystem.init();
+        mLocalDevices.add(audioSystem);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+                HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        mHdmiControlService.setSystemAudioActivated(true);
+
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        HdmiCecMessage keyPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage keyReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(keyPressed);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(keyReleased);
+    }
+
+    @Test
     public void handleSetStreamPath_broadcastsActiveSource() {
         HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
                 mPlaybackPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 48e70fe..b30118c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -141,7 +141,7 @@
                 new HdmiControlService(context, Collections.emptyList(),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return isControlEnabled;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 82c3401..5246107 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -136,7 +136,7 @@
                         super.wakeUp();
                     }
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index a08e398..4e5336e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -68,7 +68,7 @@
                 Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
                 new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
-            boolean isControlEnabled() {
+            boolean isCecControlEnabled() {
                 return true;
             }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 8f6bee1..ef2b212 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -44,6 +44,7 @@
 import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
 import android.hardware.hdmi.IHdmiControlStatusChangeListener;
 import android.hardware.hdmi.IHdmiVendorCommandListener;
+import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -58,7 +59,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -86,8 +89,12 @@
     private HdmiPortInfo[] mHdmiPortInfo;
     private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
 
+    @Mock protected AudioManager mAudioManager;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
@@ -132,6 +139,7 @@
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlServiceSpy.setPowerManager(mPowerManager);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.setAudioManager(mAudioManager);
 
         mTestLooper.dispatchAll();
     }
@@ -195,7 +203,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mNativeWrapper.clearResultMessages();
 
         assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
@@ -228,7 +236,7 @@
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
         mTestLooper.dispatchAll();
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mNativeWrapper.clearResultMessages();
         mTestLooper.dispatchAll();
 
@@ -285,11 +293,11 @@
         int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
         mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl())
                 .isEqualTo(volumeControlEnabled);
     }
@@ -300,12 +308,12 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 volumeControlEnabled);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 volumeControlEnabled);
@@ -320,13 +328,13 @@
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
         mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(callback.mCallbackReceived).isTrue();
         assertThat(callback.mVolumeControlEnabled).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(callback.mVolumeControlEnabled).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
     }
@@ -423,7 +431,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
     }
@@ -433,7 +441,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
@@ -443,14 +451,14 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
 
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
@@ -460,7 +468,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mTestLooper.dispatchAll();
 
         mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -478,7 +486,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -500,7 +508,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -517,7 +525,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -624,11 +632,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvOn() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -647,11 +655,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvStandby() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -670,11 +678,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToOn() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -693,11 +701,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToStandby() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -1026,6 +1034,48 @@
                 .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
     }
 
+    @Test
+    public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+        // Initialize the local devices excluding the audio system.
+        mHdmiControlServiceSpy.clearCecLocalDevices();
+        mLocalDevices.remove(mAudioSystemDeviceSpy);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+
+        // Enable the setting and check if the audio system local device is found in the network.
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+    }
+
+    @Test
+    public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+        // Initialize the local devices excluding the audio system.
+        mHdmiControlServiceSpy.clearCecLocalDevices();
+        mLocalDevices.remove(mAudioSystemDeviceSpy);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+
+        // Enable the setting and check if the audio system local device is found in the network.
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+
+        // Disable the setting and check if the audio system local device is removed from the
+        // network.
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+    }
+
     protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
 
         private boolean mCanGoToStandby;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index c07d4be..f719ca1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -99,7 +99,7 @@
                 new HdmiControlService(context, Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index f5bf30b..be62df8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -151,7 +151,7 @@
                         Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 6590a2b..ecd9d89 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.input
 
+import android.bluetooth.BluetoothDevice
 import android.content.Context
 import android.content.ContextWrapper
 import android.hardware.BatteryState.STATUS_CHARGING
@@ -33,6 +34,8 @@
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
 import androidx.test.InstrumentationRegistry
+import com.android.server.input.BatteryController.BluetoothBatteryManager
+import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
 import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
 import com.android.server.input.BatteryController.UEventManager
 import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
@@ -52,6 +55,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.notNull
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
@@ -172,6 +176,8 @@
         const val SECOND_DEVICE_ID = 11
         const val USI_DEVICE_ID = 101
         const val SECOND_USI_DEVICE_ID = 102
+        const val BT_DEVICE_ID = 100001
+        const val SECOND_BT_DEVICE_ID = 100002
         const val TIMESTAMP = 123456789L
     }
 
@@ -184,6 +190,8 @@
     private lateinit var iInputManager: IInputManager
     @Mock
     private lateinit var uEventManager: UEventManager
+    @Mock
+    private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
 
     private lateinit var batteryController: BatteryController
     private lateinit var context: Context
@@ -203,11 +211,13 @@
         addInputDevice(DEVICE_ID)
         addInputDevice(SECOND_DEVICE_ID)
 
-        batteryController = BatteryController(context, native, testLooper.looper, uEventManager)
+        batteryController = BatteryController(context, native, testLooper.looper, uEventManager,
+            bluetoothBatteryManager)
         batteryController.systemRunning()
         val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
         verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
         devicesChangedListener = listenerCaptor.value
+        testLooper.dispatchAll()
     }
 
     private fun notifyDeviceChanged(
@@ -230,7 +240,7 @@
     private fun addInputDevice(
             deviceId: Int,
         hasBattery: Boolean = true,
-        supportsUsi: Boolean = false
+        supportsUsi: Boolean = false,
     ) {
         deviceGenerationMap[deviceId] = 0
         notifyDeviceChanged(deviceId, hasBattery, supportsUsi)
@@ -634,4 +644,125 @@
         assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
             matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
     }
+
+    @Test
+    fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn("11:22:33:44:55:66")
+        addInputDevice(BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        addInputDevice(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+
+        // Ensure that a BT battery listener is not added when there are no monitored BT devices.
+        verify(bluetoothBatteryManager, never()).addListener(any())
+
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+
+        // The BT battery listener is added when the first BT input device is monitored.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+
+        // The BT listener is only added once for all BT devices.
+        batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, times(1)).addListener(any())
+
+        // The BT listener is only removed when there are no monitored BT devices.
+        batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, never()).removeListener(any())
+
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn(null)
+        notifyDeviceChanged(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        verify(bluetoothBatteryManager).removeListener(bluetoothListener.value)
+    }
+
+    @Test
+    fun testNotifiesBluetoothBatteryChanges() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+        // When the state has not changed, the listener is not notified again.
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(25)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f)
+    }
+
+    @Test
+    fun testBluetoothBatteryIsPrioritized() {
+        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+        // When the device is first monitored and both native and BT battery is available,
+        // the latter is used.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        verify(uEventManager).addListener(uEventListener.capture(), any())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+            matchesState(BT_DEVICE_ID, capacity = 0.21f))
+
+        // If only the native battery state changes the listener is not notified.
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(97)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+            matchesState(BT_DEVICE_ID, capacity = 0.21f))
+    }
+
+    @Test
+    fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() {
+        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        verify(uEventManager).addListener(uEventListener.capture(), any())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+        // Fall back to the native state when BT is off.
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF")))
+            .thenReturn(BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.98f)
+
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(22)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
+
+        // Fall back to the native state when BT battery is unknown.
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF")))
+            .thenReturn(BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.98f)
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index c81db92..6258d6d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2014,6 +2014,50 @@
     }
 
     @Test
+    public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        advanceTime(500);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+        advanceTime(500);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+    }
+
+    @Test
     public void testLastSleepTime_notUpdatedWhenDreaming() {
         createService();
         startSystem();
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 153d746..0d6bb8a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -24,6 +24,7 @@
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_GEO;
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_TELEPHONY;
+import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_UNKNOWN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,11 +52,11 @@
 
     /**
      * Tests {@link TimeCapabilitiesAndConfig} behavior in different scenarios when auto detection
-     * is supported (and geo detection is supported)
+     * is supported (both telephony and geo detection are supported)
      */
     @Test
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
-    public void test_autoDetectionSupported_capabilitiesAndConfiguration(
+    public void test_telephonyAndGeoSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
         ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
                 .setUserId(ARBITRARY_USER_ID)
@@ -72,18 +73,20 @@
 
         boolean userRestrictionsExpected = !(userConfigAllowed || bypassUserPolicyChecks);
 
-        // Auto-detection enabled.
+        // Auto-detection enabled, location enabled.
         {
-            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
                     .build();
-            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, config.getDetectionMode());
 
-            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -99,24 +102,58 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection enabled, location disabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertFalse(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_APPLICABLE,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This has user privacy implications so it is not restricted in the same way as others.
+            assertEquals(CAPABILITY_NOT_APPLICABLE,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
 
         // Auto-detection disabled.
         {
-            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(false)
                     .build();
-            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
-            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
+            assertFalse(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
 
-            TimeZoneCapabilities capabilities =
-                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -132,7 +169,7 @@
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -161,18 +198,20 @@
 
         boolean userRestrictionsExpected = !(userConfigAllowed || bypassUserPolicyChecks);
 
-        // Auto-detection enabled.
+        // Auto-detection enabled, location enabled.
         {
-            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
                     .build();
-            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
-            assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
 
-            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -183,7 +222,36 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection enabled, location disabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertFalse(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
+            }
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -219,11 +287,11 @@
 
     /**
      * Tests {@link TimeCapabilitiesAndConfig} behavior in different scenarios when auto detection
-     * is supported (and geo detection is not supported).
+     * is supported (telephony only).
      */
     @Test
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
-    public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
+    public void test_onlyTelephonySupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
         ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
                 .setUserId(ARBITRARY_USER_ID)
@@ -242,16 +310,16 @@
 
         // Auto-detection enabled.
         {
-            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(true)
                     .build();
-            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
 
-            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -266,24 +334,23 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
 
         // Auto-detection disabled.
         {
-            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(false)
                     .build();
-            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
-            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
+            assertFalse(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
 
-            TimeZoneCapabilities capabilities =
-                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -296,12 +363,137 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
     }
 
+    /**
+     * Tests {@link TimeCapabilitiesAndConfig} behavior in different scenarios when auto detection
+     * is supported (only geo detection)
+     */
+    @Test
+    @Parameters({ "true,true", "true,false", "false,true", "false,false" })
+    public void test_onlyGeoSupported_capabilitiesAndConfiguration(
+            boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(userConfigAllowed)
+                .setTelephonyDetectionFeatureSupported(false)
+                .setGeoDetectionFeatureSupported(true)
+                .setGeoDetectionRunInBackgroundEnabled(false)
+                .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(false)
+                .build();
+
+        boolean userRestrictionsExpected = !(userConfigAllowed || bypassUserPolicyChecks);
+
+        // Auto-detection enabled, location enabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertFalse(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_APPLICABLE,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This capability is always "not supported" if geo detection is the only mechanism.
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertFalse(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection enabled, location disabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertFalse(config.getLocationEnabledSetting());
+            assertFalse(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_UNKNOWN, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_APPLICABLE,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This capability is always "not supported" if geo detection is the only mechanism.
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertFalse(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection disabled.
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
+
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This capability is always "not supported" if geo detection is the only mechanism.
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
+            assertFalse(configuration.isAutoDetectionEnabled());
+            assertFalse(configuration.isGeoDetectionEnabled());
+        }
+    }
+
     @Test
     public void test_telephonyFallbackSupported() {
         ConfigurationInternal config = new ConfigurationInternal.Builder()
@@ -317,7 +509,10 @@
         assertTrue(config.isTelephonyFallbackSupported());
     }
 
-    /** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
+    /**
+     * Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabledSetting()}
+     * is true.
+     */
     @Test
     public void test_geoDetectionRunInBackgroundEnabled() {
         ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index bcdc65c..1e72369 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -111,7 +111,7 @@
     }
 
     @Override
-    public void enableTelephonyTimeZoneFallback() {
+    public void enableTelephonyTimeZoneFallback(String reason) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index ea801e8..8207c19 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -158,7 +158,7 @@
                 metricsTimeZoneDetectorState.isGeoDetectionSupported());
         assertEquals(configurationInternal.isTelephonyFallbackSupported(),
                 metricsTimeZoneDetectorState.isTelephonyTimeZoneFallbackSupported());
-        assertEquals(configurationInternal.getGeoDetectionRunInBackgroundEnabled(),
+        assertEquals(configurationInternal.getGeoDetectionRunInBackgroundEnabledSetting(),
                 metricsTimeZoneDetectorState.getGeoDetectionRunInBackgroundEnabled());
         assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(),
                 metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled());
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index b991c5a..1c014d1 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -29,6 +29,9 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
 
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
@@ -65,6 +68,7 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
 import android.os.HandlerThread;
+import android.service.timezone.TimeZoneProviderStatus;
 
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -880,7 +884,7 @@
         TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus(
                 DETECTOR_STATUS_RUNNING,
                 TELEPHONY_ALGORITHM_RUNNING_STATUS,
-                LocationTimeZoneAlgorithmStatus.UNKNOWN);
+                LocationTimeZoneAlgorithmStatus.RUNNING_NOT_REPORTED);
         script.verifyCachedDetectorStatus(expectedInitialDetectorStatus);
 
         LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus(
@@ -1148,7 +1152,7 @@
     }
 
     @Test
-    public void testTelephonyFallback() {
+    public void testTelephonyFallback_enableTelephonyTimeZoneFallbackCalled() {
         ConfigurationInternal config = new ConfigurationInternal.Builder(
                 CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .setTelephonyFallbackSupported(true)
@@ -1178,19 +1182,21 @@
 
         // Receiving an "uncertain" geolocation suggestion should have no effect.
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(true);
         }
 
         // Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent =
                     createCertainLocationAlgorithmEvent("Europe/London");
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
         }
@@ -1214,17 +1220,19 @@
         // Geolocation suggestions should continue to be used as normal (previous telephony
         // suggestions are not used, even when the geolocation suggestion is uncertain).
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent certainLocationAlgorithmEvent =
                     createCertainLocationAlgorithmEvent("Europe/Rome");
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
                     .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
 
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
                     createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false);
 
@@ -1246,19 +1254,21 @@
 
         // Make the geolocation algorithm uncertain.
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
                     .verifyTelephonyFallbackIsEnabled(true);
         }
 
         // Make the geolocation algorithm certain, disabling telephony fallback.
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent =
                     createCertainLocationAlgorithmEvent("Europe/Lisbon");
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
 
@@ -1267,9 +1277,10 @@
         // Demonstrate what happens when geolocation is uncertain when telephony fallback is
         // enabled.
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false)
                     .simulateEnableTelephonyFallback()
@@ -1279,6 +1290,132 @@
     }
 
     @Test
+    public void testTelephonyFallback_locationAlgorithmEventSuggestsFallback() {
+        ConfigurationInternal config = new ConfigurationInternal.Builder(
+                CONFIG_AUTO_ENABLED_GEO_ENABLED)
+                .setTelephonyFallbackSupported(true)
+                .build();
+
+        Script script = new Script()
+                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(config)
+                .resetConfigurationTracking();
+
+        // Confirm initial state is as expected.
+        script.verifyTelephonyFallbackIsEnabled(true)
+                .verifyTimeZoneNotChanged();
+
+        // Although geolocation detection is enabled, telephony fallback should be used initially
+        // and until a suitable "certain" geolocation suggestion is received.
+        {
+            TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+                    SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                    "Europe/Paris");
+            script.simulateIncrementClock()
+                    .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+                    .verifyTimeZoneChangedAndReset(telephonySuggestion)
+                    .verifyTelephonyFallbackIsEnabled(true);
+        }
+
+        // Receiving an "uncertain" geolocation suggestion without a status should have no effect.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(true);
+        }
+
+        // Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/London");
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
+                    .verifyTelephonyFallbackIsEnabled(false);
+        }
+
+        // Used to record the last telephony suggestion received, which will be used when fallback
+        // takes place.
+        TelephonyTimeZoneSuggestion lastTelephonySuggestion;
+
+        // Telephony suggestions should now be ignored and geolocation detection is "in control".
+        {
+            TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+                    SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                    "Europe/Berlin");
+            script.simulateIncrementClock()
+                    .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(false);
+            lastTelephonySuggestion = telephonySuggestion;
+        }
+
+        // Geolocation suggestions should continue to be used as normal (previous telephony
+        // suggestions are not used, even when the geolocation suggestion is uncertain).
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent certainLocationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Rome");
+            script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
+                    .verifyTelephonyFallbackIsEnabled(false);
+
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+                    createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(false);
+
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent certainLocationAlgorithmEvent2 =
+                    createCertainLocationAlgorithmEvent("Europe/Rome");
+            script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent2)
+                    // No change needed, device will already be set to Europe/Rome.
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(false);
+        }
+
+        // Enable telephony fallback via a LocationAlgorithmEvent containing an "uncertain"
+        // suggestion.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            TimeZoneProviderStatus primaryProviderReportedStatus =
+                    new TimeZoneProviderStatus.Builder()
+                            .setLocationDetectionDependencyStatus(
+                                    DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS)
+                            .setConnectivityDependencyStatus(DEPENDENCY_STATUS_UNKNOWN)
+                            .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
+                            .build();
+            LocationAlgorithmEvent uncertainEventBlockedBySettings =
+                    createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus);
+            script.simulateLocationAlgorithmEvent(uncertainEventBlockedBySettings)
+                    .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
+                    .verifyTelephonyFallbackIsEnabled(true);
+        }
+
+        // Make the geolocation algorithm certain, disabling telephony fallback.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Lisbon");
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
+                    .verifyTelephonyFallbackIsEnabled(false);
+        }
+    }
+
+    @Test
     public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() {
         ConfigurationInternal config = new ConfigurationInternal.Builder(
                 CONFIG_AUTO_ENABLED_GEO_ENABLED)
@@ -1297,9 +1434,10 @@
 
         // Receiving an "uncertain" geolocation suggestion should have no effect.
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(true);
         }
@@ -1307,9 +1445,10 @@
         // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
         // to
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(true);
         }
@@ -1319,16 +1458,18 @@
         // Geolocation suggestions should continue to be used as normal (previous telephony
         // suggestions are not used, even when the geolocation suggestion is uncertain).
         {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent certainEvent =
                     createCertainLocationAlgorithmEvent("Europe/Rome");
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(certainEvent)
+            script.simulateLocationAlgorithmEvent(certainEvent)
                     .verifyTimeZoneChangedAndReset(certainEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
 
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
             LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent();
-            script.simulateIncrementClock()
-                    .simulateLocationAlgorithmEvent(uncertainEvent)
+            script.simulateLocationAlgorithmEvent(uncertainEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false);
 
@@ -1549,9 +1690,16 @@
     }
 
     private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() {
+        TimeZoneProviderStatus primaryProviderReportedStatus = null;
+        return createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus);
+    }
+
+    private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent(
+            TimeZoneProviderStatus primaryProviderReportedStatus) {
         GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion();
         LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
-                DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null,
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_UNCERTAIN, primaryProviderReportedStatus,
                 PROVIDER_STATUS_NOT_PRESENT, null);
         LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
         event.addDebugInfo("Test uncertain event");
@@ -1744,11 +1892,12 @@
         }
 
         /**
-         * Simulates the time zone detection strategty receiving a signal that allows it to do
+         * Simulates the time zone detection strategy receiving a signal that allows it to do
          * telephony fallback.
          */
         Script simulateEnableTelephonyFallback() {
-            mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+            mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(
+                    "simulateEnableTelephonyFallback()");
             return this;
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index afec085..d54d1fe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,10 +1101,6 @@
                 new NotificationChannel("id", "name", IMPORTANCE_HIGH);
         mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
 
-        // pretend only this following part is called by the app (system permissions are required to
-        // update the notification channel on behalf of the user above)
-        mService.isSystemUid = false;
-
         // Recreating with a lower importance leaves channel unchanged.
         final NotificationChannel dupeChannel =
                 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1130,46 +1126,6 @@
     }
 
     @Test
-    public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
-        // Confirm that when createNotificationChannels is called from the relevant app and not
-        // system, then it cannot set fields that can't be set by apps
-        mService.isSystemUid = false;
-
-        final NotificationChannel channel =
-                new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
-        channel.setBypassDnd(true);
-        channel.setAllowBubbles(true);
-
-        mBinderService.createNotificationChannels(PKG,
-                new ParceledListSlice(Arrays.asList(channel)));
-
-        final NotificationChannel createdChannel =
-                mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
-        assertFalse(createdChannel.canBypassDnd());
-        assertFalse(createdChannel.canBubble());
-    }
-
-    @Test
-    public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
-        // Confirm that when createNotificationChannels is called from system,
-        // then it can set fields that can't be set by apps
-        mService.isSystemUid = true;
-
-        final NotificationChannel channel =
-                new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
-        channel.setBypassDnd(true);
-        channel.setAllowBubbles(true);
-
-        mBinderService.createNotificationChannels(PKG,
-                new ParceledListSlice(Arrays.asList(channel)));
-
-        final NotificationChannel createdChannel =
-                mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
-        assertTrue(createdChannel.canBypassDnd());
-        assertTrue(createdChannel.canBubble());
-    }
-
-    @Test
     public void testBlockedNotifications_suspended() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
 
@@ -3132,8 +3088,6 @@
 
     @Test
     public void testDeleteChannelGroupChecksForFgses() throws Exception {
-        // the setup for this test requires it to seem like it's coming from the app
-        mService.isSystemUid = false;
         when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         CountDownLatch latch = new CountDownLatch(2);
@@ -3146,7 +3100,7 @@
             ParceledListSlice<NotificationChannel> pls =
                     new ParceledListSlice(ImmutableList.of(notificationChannel));
             try {
-                mBinderService.createNotificationChannels(PKG, pls);
+                mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -3165,10 +3119,8 @@
                 ParceledListSlice<NotificationChannel> pls =
                         new ParceledListSlice(ImmutableList.of(notificationChannel));
                 try {
-                    // Because existing channels won't have their groups overwritten when the call
-                    // is from the app, this call won't take the channel out of the group
-                    mBinderService.createNotificationChannels(PKG, pls);
-                    mBinderService.deleteNotificationChannelGroup(PKG, "group");
+                mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+                mBinderService.deleteNotificationChannelGroup(PKG, "group");
                 } catch (RemoteException e) {
                     throw new RuntimeException(e);
                 }
@@ -8729,7 +8681,7 @@
         assertEquals("friend", friendChannel.getConversationId());
         assertEquals(null, original.getConversationId());
         assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
-        assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
+        assertFalse(friendChannel.canBubble()); // can't be modified by app
         assertFalse(original.getId().equals(friendChannel.getId()));
         assertNotNull(friendChannel.getId());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 1ab7d7e..a410eed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1206,6 +1206,25 @@
         }
     }
 
+    @Test
+    public void testFinishActivityIfPossible_sendResultImmediatelyIfResumed() {
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task);
+        final TaskFragment taskFragment2 = createTaskFragmentWithActivity(task);
+        final ActivityRecord resultToActivity = taskFragment1.getTopMostActivity();
+        final ActivityRecord targetActivity = taskFragment2.getTopMostActivity();
+        resultToActivity.setState(RESUMED, "test");
+        targetActivity.setState(RESUMED, "test");
+        targetActivity.resultTo = resultToActivity;
+
+        clearInvocations(mAtm.getLifecycleManager());
+        targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
+        waitUntilHandlersIdle();
+
+        verify(resultToActivity).sendResult(anyInt(), eq(null), anyInt(), anyInt(), any(), eq(null),
+                anyBoolean());
+    }
+
     /**
      * Verify that complete finish request for non-finishing activity is invalid.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index f3f56e0..dc3515d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -114,6 +114,20 @@
     }
 
     @Test
+    public void backTypeBackToHomeDifferentUser() {
+        Task taskA = createTask(mDefaultDisplay);
+        ActivityRecord recordA = createActivityRecord(taskA);
+        Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+        doReturn(false).when(taskA).showToCurrentUser();
+
+        withSystemCallback(createTopTaskWithActivity());
+        BackNavigationInfo backNavigationInfo = startBackNavigation();
+        assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+    }
+
+    @Test
     public void backTypeCrossActivityWhenBackToPreviousActivity() {
         CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
         IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 4e796c5..8fda191 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -24,7 +24,6 @@
 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.os.Process.FIRST_APPLICATION_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -33,9 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
-import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -475,23 +472,6 @@
         doReturn(true).when(taskFragment).smallerThanMinDimension(any());
         assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
                 taskFragment.isAllowedToEmbedActivity(activity));
-
-        // Not allow to start activity across TaskFragments for result.
-        final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(taskFragment.getTask())
-                .build();
-        final ActivityRecord newActivity = new ActivityBuilder(mAtm)
-                .setUid(FIRST_APPLICATION_UID)
-                .build();
-        doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
-        doReturn(false).when(newTaskFragment).smallerThanMinDimension(any());
-        newActivity.resultTo = activity;
-        assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
-                newTaskFragment.isAllowedToEmbedActivity(newActivity));
-
-        // Allow embedding if the resultTo activity is finishing.
-        activity.finishing = true;
-        assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9090c55..aab70b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -369,7 +369,7 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit);
+        dc.mTransitionController.finishTransition(transit.getToken());
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4429aef..871030f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,12 +16,16 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_OWN_FOCUS;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -81,6 +85,9 @@
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -99,6 +106,11 @@
     @Rule
     public ExpectedException mExpectedException = ExpectedException.none();
 
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            ADD_TRUSTED_DISPLAY);
+
     @Test
     public void testAddWindowToken() {
         IBinder token = mock(IBinder.class);
@@ -396,9 +408,15 @@
     @Test
     public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+        final VirtualDisplay virtualDisplayOwnTouchMode =
+                createVirtualDisplay(/* ownFocus= */ true);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
-        assertThat(numberOfDisplays).isAtLeast(2);
+        assertThat(numberOfDisplays).isAtLeast(3);
+        final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
+                        .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+                        .count();
+        assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
 
         // Enable global touch mode (config_perDisplayFocusEnabled set to false)
         Resources mockResources = mock(Resources.class);
@@ -417,15 +435,15 @@
 
         mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
 
-        verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode(
+        verify(mWm.mInputManager, times(numberOfGlobalTouchModeDisplays)).setInTouchMode(
                 eq(!currentTouchMode), eq(callingPid), eq(callingUid),
                 /* hasPermission= */ eq(true), /* displayId= */ anyInt());
     }
 
     @Test
-    public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
+    public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
@@ -452,14 +470,47 @@
                 virtualDisplay.getDisplay().getDisplayId());
     }
 
-    private VirtualDisplay createVirtualDisplay() {
+    @Test
+    public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+        // Create one extra display
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
+        final int numberOfDisplays = mWm.mRoot.mChildren.size();
+        assertThat(numberOfDisplays).isAtLeast(2);
+
+        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(false).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+
+        // Ensure that new display touch mode state has changed.
+        verify(mWm.mInputManager).setInTouchMode(
+                !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
+                virtualDisplay.getDisplay().getDisplayId());
+    }
+
+    private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
         // Create virtual display
         Point surfaceSize = new Point(
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+        int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
+        if (ownFocus) {
+            flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
+        }
         VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
-                surfaceSize.x, surfaceSize.y,
-                DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC);
+                surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
         final int displayId = virtualDisplay.getDisplay().getDisplayId();
         mWm.mRoot.onDisplayAdded(displayId);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1348770..5a261bc65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1746,7 +1746,7 @@
         }
 
         void startTransition() {
-            mOrganizer.startTransition(mLastTransit, null);
+            mOrganizer.startTransition(mLastTransit.getToken(), null);
         }
 
         void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1759,7 +1759,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit);
+            mController.finishTransition(mLastTransit.getToken());
         }
     }
 }
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 3b50fa4..133f924 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -29,9 +29,10 @@
         "android.hardware.usb-V1.1-java",
         "android.hardware.usb-V1.2-java",
         "android.hardware.usb-V1.3-java",
-	"android.hardware.usb-V1-java",
+        "android.hardware.usb-V2-java",
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.usb.gadget-V1.1-java",
         "android.hardware.usb.gadget-V1.2-java",
+        "android.hardware.usb.gadget-V1-java",
     ],
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1c081c1..ffdb07b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -44,6 +44,7 @@
 import android.debug.AdbNotifications;
 import android.debug.AdbTransportType;
 import android.debug.IAdbTransport;
+import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbConfiguration;
@@ -54,9 +55,7 @@
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
-import android.hardware.usb.gadget.V1_0.IUsbGadget;
 import android.hardware.usb.gadget.V1_0.Status;
-import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
@@ -88,9 +87,12 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.usb.hal.gadget.UsbGadgetHal;
+import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
 import com.android.server.utils.EventLogger;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -106,6 +108,7 @@
 import java.util.NoSuchElementException;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * UsbDeviceManager manages USB state in device mode.
@@ -216,6 +219,13 @@
 
     private static EventLogger sEventLogger;
 
+    private UsbGadgetHal mUsbGadgetHal;
+
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     static {
         sDenyInterfaces = new HashSet<>();
         sDenyInterfaces.add(UsbConstants.USB_CLASS_AUDIO);
@@ -298,15 +308,11 @@
         mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
         initRndisAddress();
 
+        int operationId = sUsbOperationCount.incrementAndGet();
         boolean halNotPresent = false;
-        try {
-            IUsbGadget.getService(true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
-        } catch (NoSuchElementException e) {
-            halNotPresent = true;
-            Slog.i(TAG, "USB GADGET HAL not present in the device", e);
-        }
+
+        mUsbGadgetHal = UsbGadgetHalInstance.getInstance(this, null);
+        Slog.d(TAG, "getInstance done");
 
         mControlFds = new HashMap<>();
         FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP);
@@ -320,7 +326,7 @@
         }
         mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd);
 
-        if (halNotPresent) {
+        if (mUsbGadgetHal == null) {
             /**
              * Initialze the legacy UsbHandler
              */
@@ -334,6 +340,8 @@
                     alsaManager, permissionManager);
         }
 
+        mHandler.handlerInitDone(operationId);
+
         if (nativeIsStartRequested()) {
             if (DEBUG) Slog.d(TAG, "accessory attached at boot");
             startAccessoryMode();
@@ -455,6 +463,8 @@
     private void startAccessoryMode() {
         if (!mHasUsbAccessory) return;
 
+        int operationId = sUsbOperationCount.incrementAndGet();
+
         mAccessoryStrings = nativeGetAccessoryStrings();
         boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
@@ -475,7 +485,7 @@
                     ACCESSORY_REQUEST_TIMEOUT);
             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT),
                     ACCESSORY_HANDSHAKE_TIMEOUT);
-            setCurrentFunctions(functions);
+            setCurrentFunctions(functions, operationId);
         }
     }
 
@@ -504,6 +514,20 @@
         }
     }
 
+    public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+        Slog.println(priority, TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+        }
+    }
+
+    public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
+        Slog.e(TAG, msg, e);
+        if (pw != null) {
+            pw.println(msg + e);
+        }
+    }
+
     abstract static class UsbHandler extends Handler {
 
         // current USB state
@@ -608,6 +632,19 @@
             sendMessage(m);
         }
 
+        public boolean sendMessage(int what) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            return sendMessageDelayed(m,0);
+        }
+
+        public void sendMessage(int what, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, Object arg) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -615,6 +652,22 @@
             sendMessage(m);
         }
 
+        public void sendMessage(int what, Object arg, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            m.arg1 = operationId;
+            sendMessage(m);
+        }
+
+        public void sendMessage(int what, boolean arg, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = (arg ? 1 : 0);
+            m.arg2 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, Object arg, boolean arg1) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -623,6 +676,15 @@
             sendMessage(m);
         }
 
+        public void sendMessage(int what, long arg, boolean arg1, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            m.arg1 = (arg1 ? 1 : 0);
+            m.arg2 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, boolean arg1, boolean arg2) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -680,7 +742,7 @@
             sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
         }
 
-        private void setAdbEnabled(boolean enable) {
+        private void setAdbEnabled(boolean enable, int operationId) {
             if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
 
             if (enable) {
@@ -689,7 +751,7 @@
                 setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
             }
 
-            setEnabledFunctions(mCurrentFunctions, true);
+            setEnabledFunctions(mCurrentFunctions, true, operationId);
             updateAdbNotification(false);
         }
 
@@ -701,6 +763,8 @@
         private void updateCurrentAccessory() {
             // We are entering accessory mode if we have received a request from the host
             // and the request has not timed out yet.
+            int operationId = sUsbOperationCount.incrementAndGet();
+
             boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
 
             if (mConfigured && enteringAccessoryMode) {
@@ -732,18 +796,18 @@
                 }
             } else {
                 if (!enteringAccessoryMode) {
-                    notifyAccessoryModeExit();
+                    notifyAccessoryModeExit(operationId);
                 } else if (DEBUG) {
                     Slog.v(TAG, "Debouncing accessory mode exit");
                 }
             }
         }
 
-        private void notifyAccessoryModeExit() {
+        private void notifyAccessoryModeExit(int operationId) {
             // make sure accessory mode is off
             // and restore default functions
             Slog.d(TAG, "exited USB accessory mode");
-            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+            setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
 
             if (mCurrentAccessory != null) {
                 if (mBootCompleted) {
@@ -869,8 +933,8 @@
                     mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
         }
 
-        private void setScreenUnlockedFunctions() {
-            setEnabledFunctions(mScreenUnlockedFunctions, false);
+        private void setScreenUnlockedFunctions(int operationId) {
+            setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
         }
 
         private static class AdbTransport extends IAdbTransport.Stub {
@@ -883,7 +947,8 @@
             @Override
             public void onAdbEnabled(boolean enabled, byte transportType) {
                 if (transportType == AdbTransportType.USB) {
-                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
                 }
             }
         }
@@ -906,6 +971,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UPDATE_STATE:
+                    int operationId = sUsbOperationCount.incrementAndGet();
                     mConnected = (msg.arg1 == 1);
                     mConfigured = (msg.arg2 == 1);
 
@@ -923,9 +989,9 @@
                             // restore defaults when USB is disconnected
                             if (!mScreenLocked
                                     && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
-                                setScreenUnlockedFunctions();
+                                setScreenUnlockedFunctions(operationId);
                             } else {
-                                setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                                setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                             }
                         }
                         updateUsbFunctions();
@@ -1036,13 +1102,15 @@
                     updateUsbNotification(false);
                     break;
                 case MSG_ENABLE_ADB:
-                    setAdbEnabled(msg.arg1 == 1);
+                    setAdbEnabled(msg.arg1 == 1, msg.arg2);
                     break;
                 case MSG_SET_CURRENT_FUNCTIONS:
                     long functions = (Long) msg.obj;
-                    setEnabledFunctions(functions, false);
+                    operationId = (int) msg.arg1;
+                    setEnabledFunctions(functions, false, operationId);
                     break;
                 case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mScreenUnlockedFunctions = (Long) msg.obj;
                     if (mSettings != null) {
                         SharedPreferences.Editor editor = mSettings.edit();
@@ -1053,12 +1121,13 @@
                     }
                     if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                         // If the screen is unlocked, also set current functions.
-                        setScreenUnlockedFunctions();
+                        setScreenUnlockedFunctions(operationId);
                     } else {
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     }
                     break;
                 case MSG_UPDATE_SCREEN_LOCK:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (msg.arg1 == 1 == mScreenLocked) {
                         break;
                     }
@@ -1068,23 +1137,25 @@
                     }
                     if (mScreenLocked) {
                         if (!mConnected) {
-                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                         }
                     } else {
                         if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
                                 && mCurrentFunctions == UsbManager.FUNCTION_NONE) {
                             // Set the screen unlocked functions if current function is charging.
-                            setScreenUnlockedFunctions();
+                            setScreenUnlockedFunctions(operationId);
                         }
                     }
                     break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     // Restart the USB stack if USB transfer is enabled but no longer allowed.
                     if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true, operationId);
                     }
                     break;
                 case MSG_SYSTEM_READY:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mNotificationManager = (NotificationManager)
                             mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
@@ -1102,17 +1173,19 @@
                                         NotificationManager.IMPORTANCE_HIGH));
                     }
                     mSystemReady = true;
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_LOCALE_CHANGED:
                     updateAdbNotification(true);
                     updateUsbNotification(true);
                     break;
                 case MSG_BOOT_COMPLETED:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mBootCompleted = true;
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_USER_SWITCHED: {
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (mCurrentUser != msg.arg1) {
                         if (DEBUG) {
                             Slog.v(TAG, "Current user switched to " + msg.arg1);
@@ -1125,16 +1198,18 @@
                                     mSettings.getString(String.format(Locale.ENGLISH,
                                             UNLOCKED_CONFIG_PREF, mCurrentUser), ""));
                         }
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     }
                     break;
                 }
                 case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: {
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (DEBUG) {
-                        Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
+                        Slog.v(TAG, "Accessory mode enter timeout: " + mConnected
+                                    + " ,operationId: " + operationId);
                     }
                     if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
-                        notifyAccessoryModeExit();
+                        notifyAccessoryModeExit(operationId);
                     }
                     break;
                 }
@@ -1157,7 +1232,9 @@
             }
         }
 
-        protected void finishBoot() {
+        public abstract void handlerInitDone(int operationId);
+
+        protected void finishBoot(int operationId) {
             if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
                 if (mPendingBootBroadcast) {
                     updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
@@ -1165,9 +1242,9 @@
                 }
                 if (!mScreenLocked
                         && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
-                    setScreenUnlockedFunctions();
+                    setScreenUnlockedFunctions(operationId);
                 } else {
-                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                 }
                 if (mCurrentAccessory != null) {
                     mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
@@ -1507,7 +1584,8 @@
         /**
          * Evaluates USB function policies and applies the change accordingly.
          */
-        protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
+        protected abstract void setEnabledFunctions(long functions,
+                boolean forceRestart, int operationId);
 
         public void setAccessoryUEventTime(long accessoryConnectionStartTime) {
             mAccessoryConnectionStartTime = accessoryConnectionStartTime;
@@ -1522,6 +1600,11 @@
             mSendStringCount = 0;
             mStartAccessory = false;
         }
+
+        public abstract void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions);
+
+        public abstract void getUsbSpeedCb(int speed);
     }
 
     private static final class UsbHandlerLegacy extends UsbHandler {
@@ -1540,6 +1623,11 @@
         private String mCurrentFunctionsStr;
         private boolean mUsbDataUnlocked;
 
+        /**
+         * Keeps track of the latest setCurrentUsbFunctions request number.
+         */
+        private int mCurrentRequest = 0;
+
         UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper, context, deviceManager, alsaManager, permissionManager);
@@ -1573,6 +1661,10 @@
             }
         }
 
+        @Override
+        public void handlerInitDone(int operationId) {
+        }
+
         private void readOemUsbOverrideConfig(Context context) {
             String[] configList = context.getResources().getStringArray(
                     com.android.internal.R.array.config_oemUsbModeOverride);
@@ -1675,11 +1767,14 @@
         }
 
         @Override
-        protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
+        protected void setEnabledFunctions(long usbFunctions,
+                boolean forceRestart, int operationId) {
             boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
-                        + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions +
+                        " ,forceRestart=" + forceRestart +
+                        " ,usbDataUnlocked=" + usbDataUnlocked +
+                        " ,operationId=" + operationId);
             }
 
             if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -1775,7 +1870,6 @@
                     || !mCurrentFunctionsStr.equals(functions)
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
-                Slog.i(TAG, "Setting USB config to " + functions);
                 mCurrentFunctionsStr = functions;
                 mCurrentOemFunctions = oemFunctions;
                 mCurrentFunctionsApplied = false;
@@ -1871,15 +1965,18 @@
             if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
             return true;
         }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed){
+        }
     }
 
-    private static final class UsbHandlerHal extends UsbHandler {
-
-        /**
-         * Proxy object for the usb gadget hal daemon.
-         */
-        @GuardedBy("mGadgetProxyLock")
-        private IUsbGadget mGadgetProxy;
+    private final class UsbHandlerHal extends UsbHandler {
 
         private final Object mGadgetProxyLock = new Object();
 
@@ -1926,33 +2023,20 @@
         UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper, context, deviceManager, alsaManager, permissionManager);
+            int operationId = sUsbOperationCount.incrementAndGet();
             try {
-                ServiceNotification serviceNotification = new ServiceNotification();
-
-                boolean ret = IServiceManager.getService()
-                        .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification);
-                if (!ret) {
-                    Slog.e(TAG, "Failed to register usb gadget service start notification");
-                    return;
-                }
 
                 synchronized (mGadgetProxyLock) {
-                    mGadgetProxy = IUsbGadget.getService(true);
-                    mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
-                            USB_GADGET_HAL_DEATH_COOKIE);
                     mCurrentFunctions = UsbManager.FUNCTION_NONE;
                     mCurrentUsbFunctionsRequested = true;
                     mUsbSpeed = UsbSpeed.UNKNOWN;
                     mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
-                    mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+                    updateUsbGadgetHalVersion();
                 }
                 String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                 updateState(state);
-                updateUsbGadgetHalVersion();
             } catch (NoSuchElementException e) {
                 Slog.e(TAG, "Usb gadget hal not found", e);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Usb Gadget hal not responding", e);
             } catch (Exception e) {
                 Slog.e(TAG, "Error initializing UsbHandler", e);
             }
@@ -1965,7 +2049,7 @@
                 if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
                     Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
                     synchronized (mGadgetProxyLock) {
-                        mGadgetProxy = null;
+                        mUsbGadgetHal = null;
                     }
                 }
             }
@@ -1988,18 +2072,22 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_CHARGING_FUNCTIONS:
-                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     break;
                 case MSG_SET_FUNCTIONS_TIMEOUT:
-                    Slog.e(TAG, "Set functions timed out! no reply from usb hal");
+                    operationId = sUsbOperationCount.incrementAndGet();
+                    Slog.e(TAG, "Set functions timed out! no reply from usb hal"
+                                + " ,operationId:" + operationId);
                     if (msg.arg1 != 1) {
                         // Set this since default function may be selected from Developer options
-                        setEnabledFunctions(mScreenUnlockedFunctions, false);
+                        setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
                     }
                     break;
                 case MSG_GET_CURRENT_USB_FUNCTIONS:
                     Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS");
                     mCurrentUsbFunctionsReceived = true;
+                    operationId = msg.arg2;
 
                     if (mCurrentUsbFunctionsRequested) {
                         Slog.i(TAG, "updating mCurrentFunctions");
@@ -2009,91 +2097,71 @@
                                 "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
                         mCurrentFunctionsApplied = msg.arg1 == 1;
                     }
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_FUNCTION_SWITCH_TIMEOUT:
                     /**
                      * Dont force to default when the configuration is already set to default.
                      */
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (msg.arg1 != 1) {
                         // Set this since default function may be selected from Developer options
-                        setEnabledFunctions(mScreenUnlockedFunctions, false);
+                        setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
                     }
                     break;
                 case MSG_GADGET_HAL_REGISTERED:
                     boolean preexisting = msg.arg1 == 1;
+                    operationId = sUsbOperationCount.incrementAndGet();
                     synchronized (mGadgetProxyLock) {
                         try {
-                            mGadgetProxy = IUsbGadget.getService();
-                            mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
-                                    USB_GADGET_HAL_DEATH_COOKIE);
+                            mUsbGadgetHal = UsbGadgetHalInstance.getInstance(mUsbDeviceManager,
+                                    null);
                             if (!mCurrentFunctionsApplied && !preexisting) {
-                                setEnabledFunctions(mCurrentFunctions, false);
+                                setEnabledFunctions(mCurrentFunctions, false, operationId);
                             }
                         } catch (NoSuchElementException e) {
                             Slog.e(TAG, "Usb gadget hal not found", e);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Usb Gadget hal not responding", e);
                         }
                     }
                     break;
                 case MSG_RESET_USB_GADGET:
                     synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null");
+                        if (mUsbGadgetHal == null) {
+                            Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null");
                             break;
                         }
 
                         try {
-                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
-                                    android.hardware.usb.gadget.V1_1.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            gadgetProxy.reset();
-                        } catch (RemoteException e) {
+                            mUsbGadgetHal.reset();
+                        } catch (Exception e) {
                             Slog.e(TAG, "reset Usb Gadget failed", e);
                         }
                     }
                     break;
                 case MSG_UPDATE_USB_SPEED:
-                    synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "mGadgetProxy is null");
-                            break;
-                        }
+                    operationId = sUsbOperationCount.incrementAndGet();
+                    if (mUsbGadgetHal == null) {
+                        Slog.e(TAG, "mGadgetHal is null, operationId:" + operationId);
+                        break;
+                    }
 
-                        try {
-                            android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
-                                    android.hardware.usb.gadget.V1_2.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            if (gadgetProxy != null) {
-                                gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "get UsbSpeed failed", e);
-                        }
+                    try {
+                        mUsbGadgetHal.getUsbSpeed(operationId);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "get UsbSpeed failed", e);
                     }
                     break;
                 case MSG_UPDATE_HAL_VERSION:
-                    synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "mGadgetProxy is null");
-                            break;
+                    if (mUsbGadgetHal == null) {
+                        Slog.e(TAG, "mUsbGadgetHal is null");
+                        break;
+                    }
+                    else {
+                        try {
+                            mCurrentGadgetHalVersion = mUsbGadgetHal.getGadgetHalVersion();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "update Usb gadget version failed", e);
                         }
-
-                        android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
-                                android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
-                        if (gadgetProxy == null) {
-                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxyV1By1 =
-                                    android.hardware.usb.gadget.V1_1.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            if (gadgetProxyV1By1 == null) {
-                                mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
-                                break;
-                            }
-                            mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_1;
-                            break;
-                        }
-                        mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2;
                     }
                     break;
                 default:
@@ -2101,56 +2169,31 @@
             }
         }
 
-        private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
-            int mRequest;
-            long mFunctions;
-            boolean mChargingFunctions;
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
 
-            UsbGadgetCallback() {
+            if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
+                  || (mFunctions != functions)) {
+                return;
             }
 
-            UsbGadgetCallback(int request, long functions,
-                    boolean chargingFunctions) {
-                mRequest = request;
-                mFunctions = functions;
-                mChargingFunctions = chargingFunctions;
-            }
-
-            @Override
-            public void setCurrentUsbFunctionsCb(long functions,
-                    int status) {
-                /**
-                 * Callback called for a previous setCurrenUsbFunction
-                 */
-                if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
-                        || (mFunctions != functions)) {
-                    return;
-                }
-
-                removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
-                Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
-                if (status == Status.SUCCESS) {
-                    mCurrentFunctionsApplied = true;
-                } else if (!mChargingFunctions) {
-                    Slog.e(TAG, "Setting default fuctions");
-                    sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
-                }
-            }
-
-            @Override
-            public void getCurrentUsbFunctionsCb(long functions,
-                    int status) {
-                sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
-                        status == Status.FUNCTIONS_APPLIED);
-            }
-
-            @Override
-            public void getUsbSpeedCb(int speed) {
-                mUsbSpeed = speed;
+            removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+            Slog.i(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
+            if (status == Status.SUCCESS) {
+                mCurrentFunctionsApplied = true;
+            } else if (!mChargingFunctions) {
+                Slog.e(TAG, "Setting default fuctions");
+                sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
             }
         }
 
-        private void setUsbConfig(long config, boolean chargingFunctions) {
+        @Override
+        public void getUsbSpeedCb(int speed) {
+            mUsbSpeed = speed;
+        }
+
+        private void setUsbConfig(long config, boolean chargingFunctions, int operationId) {
             if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
             /**
              * Cancel any ongoing requests, if present.
@@ -2160,8 +2203,8 @@
             removeMessages(MSG_SET_CHARGING_FUNCTIONS);
 
             synchronized (mGadgetProxyLock) {
-                if (mGadgetProxy == null) {
-                    Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
+                if (mUsbGadgetHal == null) {
+                    Slog.e(TAG, "setUsbConfig mUsbGadgetHal is null");
                     return;
                 }
                 try {
@@ -2178,10 +2221,9 @@
                         LocalServices.getService(AdbManagerInternal.class)
                                 .stopAdbdForTransport(AdbTransportType.USB);
                     }
-                    UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
-                            config, chargingFunctions);
-                    mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
-                            SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
+                    mUsbGadgetHal.setCurrentUsbFunctions(mCurrentRequest,
+                            config, chargingFunctions,
+                            SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS, operationId);
                     sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
                             SET_FUNCTIONS_TIMEOUT_MS);
                     if (mConnected) {
@@ -2190,17 +2232,19 @@
                                 SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
                     }
                     if (DEBUG) Slog.d(TAG, "timeout message queued");
-                } catch (RemoteException e) {
+                } catch (Exception e) {//RemoteException e) {
                     Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
                 }
             }
         }
 
         @Override
-        protected void setEnabledFunctions(long functions, boolean forceRestart) {
+        protected void setEnabledFunctions(long functions, boolean forceRestart, int operationId) {
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
-                        + "forceRestart=" + forceRestart);
+                Slog.d(TAG, "setEnabledFunctionsi " +
+                        "functions=" + functions +
+                        ", forceRestart=" + forceRestart +
+                        ", operationId=" + operationId);
             }
             if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
                 if ((functions & UsbManager.FUNCTION_NCM) != 0) {
@@ -2221,7 +2265,7 @@
                 functions = getAppliedFunctions(functions);
 
                 // Set the new USB configuration.
-                setUsbConfig(functions, chargingFunctions);
+                setUsbConfig(functions, chargingFunctions, operationId);
 
                 if (mBootCompleted && isUsbDataTransferActive(functions)) {
                     // Start up dependent services.
@@ -2229,6 +2273,11 @@
                 }
             }
         }
+
+        @Override
+        public void handlerInitDone(int operationId) {
+            mUsbGadgetHal.getCurrentUsbFunctions(operationId);
+        }
     }
 
     /* returns the currently attached USB accessory */
@@ -2270,6 +2319,21 @@
         return mHandler.getGadgetHalVersion();
     }
 
+    public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
+        mHandler.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+    }
+
+    public void getCurrentUsbFunctionsCb(long functions, int status) {
+        mHandler.sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
+                    status == Status.FUNCTIONS_APPLIED);
+    }
+
+    public void getUsbSpeedCb(int speed) {
+        mHandler.getUsbSpeedCb(speed);
+    }
+
     /**
      * Returns a dup of the control file descriptor for the given function.
      */
@@ -2295,7 +2359,7 @@
      *
      * @param functions The functions to set, or empty to set the charging function.
      */
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(long functions, int operationId) {
         if (DEBUG) {
             Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
         }
@@ -2312,7 +2376,7 @@
         } else if (functions == UsbManager.FUNCTION_ACCESSORY) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
         }
-        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
+        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, operationId);
     }
 
     /**
@@ -2340,7 +2404,8 @@
     }
 
     private void onAdbEnabled(boolean enabled) {
-        mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+        int operationId = sUsbOperationCount.incrementAndGet();
+        mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
     }
 
     /**
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index f8df6c6..4bb9de5 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -73,6 +73,7 @@
 import android.service.usb.UsbPortInfoProto;
 import android.service.usb.UsbPortManagerProto;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 
@@ -87,6 +88,7 @@
 import com.android.server.usb.hal.port.UsbPortHal;
 import com.android.server.usb.hal.port.UsbPortHalInstance;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
 import java.util.Objects;
@@ -754,6 +756,31 @@
         }
     }
 
+    /**
+     * Sets Compliance Warnings for simulated USB port objects.
+     */
+    public void simulateComplianceWarnings(String portId, String complianceWarningsString,
+            IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final RawPortInfo portInfo = mSimulatedPorts.get(portId);
+            if (portInfo == null) {
+                pw.println("Simulated port not found");
+                return;
+            }
+
+            IntArray complianceWarnings = new IntArray();
+            for (String s : complianceWarningsString.split("[, ]")) {
+                if (s.length() > 0) {
+                    complianceWarnings.add(Integer.parseInt(s));
+                }
+            }
+            pw.println("Simulating Compliance Warnings: portId=" + portId
+                    + " Warnings=" + complianceWarningsString);
+            portInfo.complianceWarnings = complianceWarnings.toArray();
+            updatePortsLocked(pw, null);
+        }
+    }
+
     public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
         synchronized (mLock) {
             final RawPortInfo portInfo = mSimulatedPorts.get(portId);
@@ -842,7 +869,10 @@
                         portInfo.contaminantDetectionStatus,
                         portInfo.usbDataStatus,
                         portInfo.powerTransferLimited,
-                        portInfo.powerBrickConnectionStatus, pw);
+                        portInfo.powerBrickConnectionStatus,
+                        portInfo.supportsComplianceWarnings,
+                        portInfo.complianceWarnings,
+                        pw);
             }
         } else {
             for (RawPortInfo currentPortInfo : newPortInfo) {
@@ -857,7 +887,10 @@
                         currentPortInfo.contaminantDetectionStatus,
                         currentPortInfo.usbDataStatus,
                         currentPortInfo.powerTransferLimited,
-                        currentPortInfo.powerBrickConnectionStatus, pw);
+                        currentPortInfo.powerBrickConnectionStatus,
+                        currentPortInfo.supportsComplianceWarnings,
+                        currentPortInfo.complianceWarnings,
+                        pw);
             }
         }
 
@@ -880,6 +913,9 @@
                     handlePortRemovedLocked(portInfo, pw);
                     break;
             }
+            if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_CHANGED) {
+                handlePortComplianceWarningLocked(portInfo, pw);
+            }
         }
     }
 
@@ -896,6 +932,8 @@
             int usbDataStatus,
             boolean powerTransferLimited,
             int powerBrickConnectionStatus,
+            boolean supportsComplianceWarnings,
+            @NonNull int[] complianceWarnings,
             IndentingPrintWriter pw) {
         // Only allow mode switch capability for dual role ports.
         // Validate that the current mode matches the supported modes we expect.
@@ -949,13 +987,15 @@
             portInfo = new PortInfo(mContext.getSystemService(UsbManager.class),
                 portId, supportedModes, supportedContaminantProtectionModes,
                 supportsEnableContaminantPresenceProtection,
-                supportsEnableContaminantPresenceDetection);
+                supportsEnableContaminantPresenceDetection,
+                supportsComplianceWarnings);
             portInfo.setStatus(currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
                     contaminantDetectionStatus, usbDataStatus,
-                    powerTransferLimited, powerBrickConnectionStatus);
+                    powerTransferLimited, powerBrickConnectionStatus,
+                    complianceWarnings);
             mPorts.put(portId, portInfo);
         } else {
             // Validate that ports aren't changing definition out from under us.
@@ -987,13 +1027,13 @@
                         + ", current=" + supportsEnableContaminantPresenceDetection);
             }
 
-
             if (portInfo.setStatus(currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
                     contaminantDetectionStatus, usbDataStatus,
-                    powerTransferLimited, powerBrickConnectionStatus)) {
+                    powerTransferLimited, powerBrickConnectionStatus,
+                    complianceWarnings)) {
                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
             } else {
                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -1019,6 +1059,11 @@
         handlePortLocked(portInfo, pw);
     }
 
+    private void handlePortComplianceWarningLocked(PortInfo portInfo, IndentingPrintWriter pw) {
+        logAndPrint(Log.INFO, pw, "USB port compliance warning changed: " + portInfo);
+        sendComplianceWarningBroadcastLocked(portInfo);
+    }
+
     private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
         handlePortLocked(portInfo, pw);
@@ -1056,6 +1101,23 @@
                 Manifest.permission.MANAGE_USB));
     }
 
+    private void sendComplianceWarningBroadcastLocked(PortInfo portInfo) {
+        if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_UNCHANGED) {
+            return;
+        }
+        final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+        intent.addFlags(
+                Intent.FLAG_RECEIVER_FOREGROUND |
+                        Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort));
+        intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
+
+        // Guard against possible reentrance by posting the broadcast from the handler
+        // instead of from within the critical section.
+        mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                Manifest.permission.MANAGE_USB));
+    }
+
     private void enableContaminantDetectionIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) {
         if (!mConnected.containsKey(portInfo.mUsbPort.getId())) {
             return;
@@ -1180,6 +1242,9 @@
         public static final int DISPOSITION_READY = 2;
         public static final int DISPOSITION_REMOVED = 3;
 
+        public static final int COMPLIANCE_WARNING_UNCHANGED = 0;
+        public static final int COMPLIANCE_WARNING_CHANGED = 1;
+
         public final UsbPort mUsbPort;
         public UsbPortStatus mUsbPortStatus;
         public boolean mCanChangeMode;
@@ -1191,15 +1256,29 @@
         public long mConnectedAtMillis;
         // 0 when port is connected. Else reports the last connected duration
         public long mLastConnectDurationMillis;
+        // default initialized to 0 which means no changes reported
+        public int mComplianceWarningChange;
 
         PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes,
                 int supportedContaminantProtectionModes,
                 boolean supportsEnableContaminantPresenceDetection,
-                boolean supportsEnableContaminantPresenceProtection) {
+                boolean supportsEnableContaminantPresenceProtection,
+                boolean supportsComplianceWarnings) {
             mUsbPort = new UsbPort(usbManager, portId, supportedModes,
                     supportedContaminantProtectionModes,
                     supportsEnableContaminantPresenceDetection,
-                    supportsEnableContaminantPresenceProtection);
+                    supportsEnableContaminantPresenceProtection,
+                    supportsComplianceWarnings);
+            mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED;
+        }
+
+        public boolean complianceWarningsChanged(@NonNull int[] complianceWarnings) {
+            if (Arrays.equals(complianceWarnings, mUsbPortStatus.getComplianceWarnings())) {
+                mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED;
+                return false;
+            }
+            mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED;
+            return true;
         }
 
         public boolean setStatus(int currentMode, boolean canChangeMode,
@@ -1221,7 +1300,8 @@
                         supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
                         UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
                         UsbPortStatus.DATA_STATUS_UNKNOWN, false,
-                        UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN);
+                        UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN,
+                        new int[] {});
                 dispositionChanged = true;
             }
 
@@ -1266,7 +1346,8 @@
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations, contaminantProtectionStatus,
                         contaminantDetectionStatus, usbDataStatus,
-                        powerTransferLimited, powerBrickConnectionStatus);
+                        powerTransferLimited, powerBrickConnectionStatus,
+                        new int[] {});
                 dispositionChanged = true;
             }
 
@@ -1281,6 +1362,62 @@
             return dispositionChanged;
         }
 
+        public boolean setStatus(int currentMode, boolean canChangeMode,
+                int currentPowerRole, boolean canChangePowerRole,
+                int currentDataRole, boolean canChangeDataRole,
+                int supportedRoleCombinations, int contaminantProtectionStatus,
+                int contaminantDetectionStatus, int usbDataStatus,
+                boolean powerTransferLimited, int powerBrickConnectionStatus,
+                @NonNull int[] complianceWarnings) {
+            boolean dispositionChanged = false;
+
+            mCanChangeMode = canChangeMode;
+            mCanChangePowerRole = canChangePowerRole;
+            mCanChangeDataRole = canChangeDataRole;
+            if (mUsbPortStatus == null
+                    || mUsbPortStatus.getCurrentMode() != currentMode
+                    || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
+                    || mUsbPortStatus.getCurrentDataRole() != currentDataRole
+                    || mUsbPortStatus.getSupportedRoleCombinations()
+                    != supportedRoleCombinations
+                    || mUsbPortStatus.getContaminantProtectionStatus()
+                    != contaminantProtectionStatus
+                    || mUsbPortStatus.getContaminantDetectionStatus()
+                    != contaminantDetectionStatus
+                    || mUsbPortStatus.getUsbDataStatus()
+                    != usbDataStatus
+                    || mUsbPortStatus.isPowerTransferLimited()
+                    != powerTransferLimited
+                    || mUsbPortStatus.getPowerBrickConnectionStatus()
+                    != powerBrickConnectionStatus) {
+                if (mUsbPortStatus == null && complianceWarnings.length > 0) {
+                    mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED;
+                }
+                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                        supportedRoleCombinations, contaminantProtectionStatus,
+                        contaminantDetectionStatus, usbDataStatus,
+                        powerTransferLimited, powerBrickConnectionStatus,
+                        complianceWarnings);
+                dispositionChanged = true;
+            } else if (complianceWarningsChanged(complianceWarnings)) {
+                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                        supportedRoleCombinations, contaminantProtectionStatus,
+                        contaminantDetectionStatus, usbDataStatus,
+                        powerTransferLimited, powerBrickConnectionStatus,
+                        complianceWarnings);
+            }
+
+            if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
+                mConnectedAtMillis = SystemClock.elapsedRealtime();
+                mLastConnectDurationMillis = 0;
+            } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
+                mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
+                mConnectedAtMillis = 0;
+            }
+
+            return dispositionChanged;
+        }
+
         void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
             long token = dump.start(idName, id);
 
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 72f6cc3..d09f729 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -622,16 +622,16 @@
     }
 
     @Override
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(long functions, int operationId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
         Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
         Preconditions.checkState(mDeviceManager != null);
-        mDeviceManager.setCurrentFunctions(functions);
+        mDeviceManager.setCurrentFunctions(functions, operationId);
     }
 
     @Override
-    public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
-        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions));
+    public void setCurrentFunction(String functions, boolean usbDataUnlocked, int operationId) {
+        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions), operationId);
     }
 
     @Override
@@ -1093,6 +1093,23 @@
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
                             "", 0);
                 }
+            } else if ("set-compliance-reasons".equals(args[0]) && args.length == 3) {
+                final String portId = args[1];
+                final String complianceWarnings = args[2];
+                if (mPortManager != null) {
+                    mPortManager.simulateComplianceWarnings(portId, complianceWarnings, pw);
+                    pw.println();
+                    mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
+                            "", 0);
+                }
+            } else if ("clear-compliance-reasons".equals(args[0]) && args.length == 2) {
+                final String portId = args[1];
+                if (mPortManager != null) {
+                    mPortManager.simulateComplianceWarnings(portId, "", pw);
+                    pw.println();
+                    mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
+                            "", 0);
+                }
             } else if ("ports".equals(args[0]) && args.length == 1) {
                 if (mPortManager != null) {
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
@@ -1142,6 +1159,17 @@
                 pw.println("  dumpsys usb set-contaminant-status \"matrix\" true");
                 pw.println("  dumpsys usb set-contaminant-status \"matrix\" false");
                 pw.println();
+                pw.println("Example simulate compliance warnings:");
+                pw.println("  dumpsys usb add-port \"matrix\" dual");
+                pw.println("  dumpsys usb set-compliance-reasons \"matrix\" <reason-list>");
+                pw.println("  dumpsys usb clear-compliance-reasons \"matrix\"");
+                pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\"");
+                pw.println("with reasons that need to be simulated.");
+                pw.println("  1: debug accessory");
+                pw.println("  2: bc12");
+                pw.println("  3: missing rp");
+                pw.println("  4: type c");
+                pw.println();
                 pw.println("Example USB device descriptors:");
                 pw.println("  dumpsys usb dump-descriptors -dump-short");
                 pw.println("  dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
new file mode 100644
index 0000000..bdfe60a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -0,0 +1,220 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_V2_0;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.IUsbGadget;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.os.ServiceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * Implements the methods to interact with AIDL USB HAL.
+ */
+public final class UsbGadgetAidl implements UsbGadgetHal {
+    private static final String TAG = UsbGadgetAidl.class.getSimpleName();
+    private static final String USB_GADGET_AIDL_SERVICE = IUsbGadget.DESCRIPTOR + "/default";
+    // Proxy object for the usb gadget hal daemon.
+    @GuardedBy("mGadgetProxyLock")
+    private IUsbGadget mGadgetProxy;
+    private final UsbDeviceManager mDeviceManager;
+    public final IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mGadgetProxyLock = new Object();
+    // Callback when the UsbDevice status is changed by the kernel.
+    private UsbGadgetCallback mUsbGadgetCallback;
+
+    public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy == null) {
+                throw new RemoteException("IUsb not initialized yet");
+            }
+        }
+        Slog.i(TAG, "USB Gadget HAL AIDL version: GADGET_HAL_V2_0");
+        return GADGET_HAL_V2_0;
+    }
+
+    @Override
+    public void systemReady() {
+    }
+
+    public void serviceDied() {
+        logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died");
+        synchronized (mGadgetProxyLock) {
+            mGadgetProxy = null;
+        }
+        connectToProxy(null);
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy != null) {
+                return;
+            }
+
+            try {
+                mGadgetProxy = IUsbGadget.Stub.asInterface(
+                        ServiceManager.waitForService(USB_GADGET_AIDL_SERVICE));
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+                        + " Did the service fail to start?", e);
+            }
+        }
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            return ServiceManager.isDeclared(USB_GADGET_AIDL_SERVICE);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb gadget Aidl hal service not found.", e);
+        }
+
+        return false;
+    }
+
+    public UsbGadgetAidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+        mDeviceManager = Objects.requireNonNull(deviceManager);
+        mPw = pw;
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void getCurrentUsbFunctions(long operationId) {
+        synchronized (mGadgetProxyLock) {
+            try {
+                mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback(), operationId);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "RemoteException while calling getCurrentUsbFunctions"
+                        + ", opID:" + operationId, e);
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void getUsbSpeed(long operationId) {
+        try {
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.getUsbSpeed(new UsbGadgetCallback(), operationId);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed"
+                    + ", opID:" + operationId, e);
+            return;
+        }
+    }
+
+    @Override
+    public void reset() {
+        try {
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.reset();
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed", e);
+            return;
+        }
+    }
+
+    @Override
+    public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+            boolean mChargingFunctions, int timeout, long operationId) {
+        try {
+            mUsbGadgetCallback = new UsbGadgetCallback(mRequest,
+                                      mFunctions, mChargingFunctions);
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback,
+                        timeout, operationId);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling setCurrentUsbFunctions: "
+                    + "mRequest=" + mRequest
+                    + ", mFunctions=" + mFunctions
+                    + ", mChargingFunctions=" + mChargingFunctions
+                    + ", timeout=" + timeout
+                    + ", opID:" + operationId, e);
+            return;
+        }
+    }
+
+    private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public int mRequest;
+        public long mFunctions;
+        public boolean mChargingFunctions;
+
+        UsbGadgetCallback() {
+        }
+
+        UsbGadgetCallback(int request, long functions,
+                boolean chargingFunctions) {
+            mRequest = request;
+            mFunctions = functions;
+            mChargingFunctions = chargingFunctions;
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                int status, long transactionId) {
+            mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+        }
+
+        @Override
+        public void getCurrentUsbFunctionsCb(long functions,
+                int status, long transactionId) {
+            mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed, long transactionId) {
+            mDeviceManager.getUsbSpeedCb(speed);
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return IUsbGadgetCallback.HASH;
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return IUsbGadgetCallback.VERSION;
+        }
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
new file mode 100644
index 0000000..267247b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -0,0 +1,138 @@
+/*
+ * 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.usb.hal.gadget;
+
+import android.annotation.IntDef;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.String;
+
+/**
+ * @hide
+ */
+public interface UsbGadgetHal {
+    /**
+     * Power role: This USB port can act as a source (provide power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SOURCE = 1;
+
+    /**
+     * Power role: This USB port can act as a sink (receive power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SINK = 2;
+
+    @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
+            HAL_POWER_ROLE_SOURCE,
+            HAL_POWER_ROLE_SINK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPowerRole{}
+
+    /**
+     * Data role: This USB port can act as a host (access data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_HOST = 1;
+
+    /**
+     * Data role: This USB port can act as a device (offer data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_DEVICE = 2;
+
+    @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
+            HAL_DATA_ROLE_HOST,
+            HAL_DATA_ROLE_DEVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbDataRole{}
+
+    /**
+     * This USB port can act as a downstream facing port (host).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_DFP = 1;
+
+    /**
+     * This USB port can act as an upstream facing port (device).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_UFP = 2;
+    @IntDef(prefix = { "HAL_MODE_" }, value = {
+            HAL_MODE_DFP,
+            HAL_MODE_UFP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPortMode{}
+
+    /**
+     * UsbPortManager would call this when the system is done booting.
+     */
+    public void systemReady();
+
+    /**
+     * This function is used to query the USB functions included in the
+     * current USB configuration.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void getCurrentUsbFunctions(long transactionId);
+
+    /**
+     * The function is used to query current USB speed.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void getUsbSpeed(long transactionId);
+
+    /**
+     * This function is used to reset USB gadget driver.
+     * Performs USB data connection reset. The connection will disconnect and
+     * reconnect.
+     */
+    public void reset();
+
+    /**
+     * Invoked to query the version of current gadget hal implementation.
+     */
+    public @UsbHalVersion int getGadgetHalVersion() throws RemoteException;
+
+    /**
+     * This function is used to set the current USB gadget configuration.
+     * The USB gadget needs to be torn down if a USB configuration is already
+     * active.
+     *
+     * @param functions list of functions defined by GadgetFunction to be
+     *                  included in the gadget composition.
+     * @param timeout The maximum time (in milliseconds) within which the
+     *                IUsbGadgetCallback needs to be returned.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void setCurrentUsbFunctions(int request, long functions,
+        boolean chargingFunctions, int timeout, long transactionId);
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
new file mode 100644
index 0000000..d268315
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
@@ -0,0 +1,48 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.hal.gadget.UsbGadgetHidl;
+import com.android.server.usb.hal.gadget.UsbGadgetAidl;
+import com.android.server.usb.UsbDeviceManager;
+
+import android.util.Log;
+/**
+ * Helper class that queries the underlying hal layer to populate UsbPortHal instance.
+ */
+public final class UsbGadgetHalInstance {
+
+    public static UsbGadgetHal getInstance(UsbDeviceManager deviceManager,
+            IndentingPrintWriter pw) {
+
+        logAndPrint(Log.DEBUG, pw, "Querying USB Gadget HAL version");
+        if (UsbGadgetAidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, pw, "USB Gadget HAL AIDL present");
+            return new UsbGadgetAidl(deviceManager, pw);
+        }
+        if (UsbGadgetHidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, pw, "USB Gadget HAL HIDL present");
+            return new UsbGadgetHidl(deviceManager, pw);
+        }
+
+        logAndPrint(Log.ERROR, pw, "USB Gadget HAL AIDL/HIDL not present");
+        return null;
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
new file mode 100644
index 0000000..3e5ecc5
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -0,0 +1,261 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_NOT_SUPPORTED;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_0;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_1;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_2;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.V1_0.Status;
+import android.hardware.usb.gadget.V1_0.IUsbGadget;
+import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
+import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+/**
+ *
+ */
+public final class UsbGadgetHidl implements UsbGadgetHal {
+    // Cookie sent for usb gadget hal death notification.
+    private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
+    // Proxy object for the usb gadget hal daemon.
+    @GuardedBy("mGadgetProxyLock")
+    private IUsbGadget mGadgetProxy;
+    private UsbDeviceManager mDeviceManager;
+    private final IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mGadgetProxyLock = new Object();
+    private UsbGadgetCallback mUsbGadgetCallback;
+
+    public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+        int version;
+        synchronized(mGadgetProxyLock) {
+            if (mGadgetProxy == null) {
+                throw new RemoteException("IUsbGadget not initialized yet");
+            }
+            if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                version = UsbManager.GADGET_HAL_V1_2;
+            } else if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                version = UsbManager.GADGET_HAL_V1_1;
+            } else {
+                version = UsbManager.GADGET_HAL_V1_0;
+            }
+            logAndPrint(Log.INFO, mPw, "USB Gadget HAL HIDL version: " + version);
+            return version;
+        }
+    }
+
+    final class DeathRecipient implements IHwBinder.DeathRecipient {
+        private final IndentingPrintWriter mPw;
+
+        DeathRecipient(IndentingPrintWriter pw) {
+            mPw = pw;
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
+                logAndPrint(Log.ERROR, mPw, "Usb Gadget hal service died cookie: " + cookie);
+                synchronized (mGadgetProxyLock) {
+                    mGadgetProxy = null;
+                }
+            }
+        }
+    }
+
+    final class ServiceNotification extends IServiceNotification.Stub {
+        @Override
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            logAndPrint(Log.INFO, mPw, "Usb gadget hal service started " + fqName + " " + name);
+            connectToProxy(null);
+        }
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy != null) {
+                return;
+            }
+
+            try {
+                mGadgetProxy = IUsbGadget.getService();
+                mGadgetProxy.linkToDeath(new DeathRecipient(pw), USB_GADGET_HAL_DEATH_COOKIE);
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+                        + " Did the service fail to start?", e);
+            } catch (RemoteException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not responding"
+                        , e);
+            }
+        }
+    }
+
+    @Override
+    public void systemReady() {
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            IUsbGadget.getService(true);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb gadget hidl hal service not found.", e);
+            return false;
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "IUSBGadget hal service present but failed to get service", e);
+        }
+
+        return true;
+    }
+
+    public UsbGadgetHidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+        mDeviceManager = Objects.requireNonNull(deviceManager);
+        mPw = pw;
+        try {
+            ServiceNotification serviceNotification = new ServiceNotification();
+
+            boolean ret = IServiceManager.getService()
+                    .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget",
+                            "", serviceNotification);
+            if (!ret) {
+                logAndPrint(Log.ERROR, pw, "Failed to register service start notification");
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "Failed to register service start notification", e);
+            return;
+        }
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void getCurrentUsbFunctions(long transactionId) {
+        try {
+            synchronized(mGadgetProxyLock) {
+                mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getCurrentUsbFunctions", e);
+            return;
+        }
+    }
+
+    @Override
+    public void getUsbSpeed(long transactionId) {
+        try {
+            synchronized(mGadgetProxyLock) {
+                if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                    android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+                    android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+                    gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw, "get UsbSpeed failed", e);
+        }
+    }
+
+    @Override
+    public void reset() {
+        try {
+            synchronized(mGadgetProxyLock) {
+                if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                    android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+                    android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+                    gadgetProxy.reset();
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed", e);
+            return;
+        }
+    }
+
+    @Override
+    public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+            boolean mChargingFunctions, int timeout, long operationId) {
+        try {
+            mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest,
+                                      mFunctions, mChargingFunctions);
+            synchronized(mGadgetProxyLock) {
+                mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, timeout);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling setCurrentUsbFunctions"
+                    + " mRequest = " + mRequest
+                    + ", mFunctions = " + mFunctions
+                    + ", timeout = " + timeout
+                    + ", mChargingFunctions = " + mChargingFunctions
+                    + ", operationId =" + operationId, e);
+            return;
+        }
+    }
+
+    private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public int mRequest;
+        public long mFunctions;
+        public boolean mChargingFunctions;
+
+        UsbGadgetCallback() {
+        }
+        UsbGadgetCallback(IndentingPrintWriter pw, int request,
+                long functions, boolean chargingFunctions) {
+            mRequest = request;
+            mFunctions = functions;
+            mChargingFunctions = chargingFunctions;
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                int status) {
+            mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+        }
+
+        @Override
+        public void getCurrentUsbFunctionsCb(long functions,
+                int status) {
+            mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed) {
+            mDeviceManager.getUsbSpeedCb(speed);
+        }
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
index 128a051..e6a3e53 100644
--- a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
+++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
@@ -40,6 +40,8 @@
     public int usbDataStatus;
     public boolean powerTransferLimited;
     public int powerBrickConnectionStatus;
+    public final boolean supportsComplianceWarnings;
+    public int[] complianceWarnings;
 
     public RawPortInfo(String portId, int supportedModes) {
         this.portId = portId;
@@ -50,9 +52,10 @@
         this.supportsEnableContaminantPresenceDetection = false;
         this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
         this.usbDataStatus = UsbPortStatus.DATA_STATUS_UNKNOWN;
-
         this.powerTransferLimited = false;
         this.powerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
+        this.supportsComplianceWarnings = false;
+        this.complianceWarnings = new int[] {};
     }
 
     public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
@@ -66,6 +69,29 @@
             int usbDataStatus,
             boolean powerTransferLimited,
             int powerBrickConnectionStatus) {
+        this(portId, supportedModes, supportedContaminantProtectionModes,
+                    currentMode, canChangeMode,
+                    currentPowerRole, canChangePowerRole,
+                    currentDataRole, canChangeDataRole,
+                    supportsEnableContaminantPresenceProtection, contaminantProtectionStatus,
+                    supportsEnableContaminantPresenceDetection, contaminantDetectionStatus,
+                    usbDataStatus, powerTransferLimited, powerBrickConnectionStatus,
+                    false, new int[] {});
+    }
+
+    public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
+            int currentMode, boolean canChangeMode,
+            int currentPowerRole, boolean canChangePowerRole,
+            int currentDataRole, boolean canChangeDataRole,
+            boolean supportsEnableContaminantPresenceProtection,
+            int contaminantProtectionStatus,
+            boolean supportsEnableContaminantPresenceDetection,
+            int contaminantDetectionStatus,
+            int usbDataStatus,
+            boolean powerTransferLimited,
+            int powerBrickConnectionStatus,
+            boolean supportsComplianceWarnings,
+            int[] complianceWarnings) {
         this.portId = portId;
         this.supportedModes = supportedModes;
         this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -84,6 +110,8 @@
         this.usbDataStatus = usbDataStatus;
         this.powerTransferLimited = powerTransferLimited;
         this.powerBrickConnectionStatus = powerBrickConnectionStatus;
+        this.supportsComplianceWarnings = supportsComplianceWarnings;
+        this.complianceWarnings = complianceWarnings;
     }
 
     @Override
@@ -109,6 +137,8 @@
         dest.writeInt(usbDataStatus);
         dest.writeBoolean(powerTransferLimited);
         dest.writeInt(powerBrickConnectionStatus);
+        dest.writeBoolean(supportsComplianceWarnings);
+        dest.writeIntArray(complianceWarnings);
     }
 
     public static final Parcelable.Creator<RawPortInfo> CREATOR =
@@ -131,6 +161,8 @@
             int usbDataStatus = in.readInt();
             boolean powerTransferLimited = in.readBoolean();
             int powerBrickConnectionStatus = in.readInt();
+            boolean supportsComplianceWarnings = in.readBoolean();
+            int[] complianceWarnings = in.createIntArray();
             return new RawPortInfo(id, supportedModes,
                     supportedContaminantProtectionModes, currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
@@ -139,7 +171,8 @@
                     contaminantProtectionStatus,
                     supportsEnableContaminantPresenceDetection,
                     contaminantDetectionStatus, usbDataStatus,
-                    powerTransferLimited, powerBrickConnectionStatus);
+                    powerTransferLimited, powerBrickConnectionStatus,
+                    supportsComplianceWarnings, complianceWarnings);
         }
 
         @Override
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 94273a3..ca11629 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -34,9 +34,12 @@
 import android.hardware.usb.IUsbCallback;
 import android.hardware.usb.PortRole;
 import android.hardware.usb.PortStatus;
+import android.hardware.usb.ComplianceWarning;
+import android.os.Build;
 import android.os.ServiceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Slog;
@@ -46,6 +49,7 @@
 import com.android.server.usb.UsbPortManager;
 import com.android.server.usb.hal.port.RawPortInfo;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.NoSuchElementException;
@@ -551,6 +555,24 @@
             return usbDataStatus;
         }
 
+        private int[] formatComplianceWarnings(int[] complianceWarnings) {
+            Objects.requireNonNull(complianceWarnings);
+            IntArray newComplianceWarnings = new IntArray();
+            Arrays.sort(complianceWarnings);
+            for (int warning : complianceWarnings) {
+                if (newComplianceWarnings.indexOf(warning) == -1
+                        && warning >= UsbPortStatus.COMPLIANCE_WARNING_OTHER) {
+                    // ComplianceWarnings range from [1, 4] in Android U
+                    if (warning > UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP) {
+                        newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+                    } else {
+                        newComplianceWarnings.add(warning);
+                    }
+                }
+            }
+            return newComplianceWarnings.toArray();
+        }
+
         @Override
         public void notifyPortStatusChange(
                android.hardware.usb.PortStatus[] currentPortStatus, int retval) {
@@ -584,7 +606,9 @@
                         current.contaminantDetectionStatus,
                         toUsbDataStatusInt(current.usbDataStatus),
                         current.powerTransferLimited,
-                        current.powerBrickStatus);
+                        current.powerBrickStatus,
+                        current.supportsComplianceWarnings,
+                        formatComplianceWarnings(current.complianceWarnings));
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: "
                         + current.portName);
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index 23d913c..10403c1 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -421,7 +421,8 @@
                         current.currentDataRole, current.canChangeDataRole,
                         false, CONTAMINANT_PROTECTION_NONE,
                         false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
-                        false, POWER_BRICK_STATUS_UNKNOWN);
+                        false, POWER_BRICK_STATUS_UNKNOWN,
+                        false, new int[] {});
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: "
                         + current.portName);
@@ -455,7 +456,8 @@
                         current.status.currentDataRole, current.status.canChangeDataRole,
                         false, CONTAMINANT_PROTECTION_NONE,
                         false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
-                        false, POWER_BRICK_STATUS_UNKNOWN);
+                        false, POWER_BRICK_STATUS_UNKNOWN,
+                        false, new int[] {});
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: "
                         + current.status.portName);
@@ -493,7 +495,8 @@
                         current.supportsEnableContaminantPresenceDetection,
                         current.contaminantDetectionStatus,
                         sUsbDataStatus,
-                        false, POWER_BRICK_STATUS_UNKNOWN);
+                        false, POWER_BRICK_STATUS_UNKNOWN,
+                        false, new int[] {});
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: "
                         + current.status_1_1.status.portName);
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index 5cb0c17..1d3b6481 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -100,56 +100,60 @@
       dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
   std::vector<dex::MethodBuilder> methods;
 
-  assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s,
-                                                       android::FileType) {
-    if (s == "layout") {
-      auto path = StringPrintf("res/%s/", s.to_string().c_str());
-      assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file,
-                                                         android::FileType) {
-        auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
-        android::ApkAssetsCookie cookie = android::kInvalidCookie;
-        auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
-        CHECK(asset);
-        CHECK(android::kInvalidCookie != cookie);
-        const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
-        CHECK(nullptr != dynamic_ref_table);
-        android::ResXMLTree xml_tree{dynamic_ref_table};
-        xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
-                       asset->getLength(),
-                       /*copy_data=*/true);
-        android::ResXMLParser parser{xml_tree};
-        parser.restart();
-        if (CanCompileLayout(&parser)) {
-          parser.restart();
-          const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
-          ResXmlVisitorAdapter adapter{&parser};
-          switch (target) {
-            case CompilationTarget::kDex: {
-              methods.push_back(compiled_view.CreateMethod(
-                  layout_name,
-                  dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
-                                 dex::TypeDescriptor::FromClassname("android.content.Context"),
-                                 dex::TypeDescriptor::Int()}));
-              DexViewBuilder builder(&methods.back());
-              builder.Start();
-              LayoutCompilerVisitor visitor{&builder};
-              adapter.Accept(&visitor);
-              builder.Finish();
-              methods.back().Encode();
-              break;
-            }
-            case CompilationTarget::kJavaLanguage: {
-              JavaLangViewBuilder builder{package_name, layout_name, target_out};
-              builder.Start();
-              LayoutCompilerVisitor visitor{&builder};
-              adapter.Accept(&visitor);
-              builder.Finish();
-              break;
-            }
-          }
-        }
-      });
-    }
+  assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) {
+      if (s == "layout") {
+          auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data());
+          assets->GetAssetsProvider()
+                  ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) {
+                      auto layout_path = StringPrintf("%s%.*s", path.c_str(),
+                                                      (int)layout_file.size(), layout_file.data());
+                      android::ApkAssetsCookie cookie = android::kInvalidCookie;
+                      auto asset = resources.OpenNonAsset(layout_path,
+                                                          android::Asset::ACCESS_RANDOM, &cookie);
+                      CHECK(asset);
+                      CHECK(android::kInvalidCookie != cookie);
+                      const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
+                      CHECK(nullptr != dynamic_ref_table);
+                      android::ResXMLTree xml_tree{dynamic_ref_table};
+                      xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(),
+                                     /*copy_data=*/true);
+                      android::ResXMLParser parser{xml_tree};
+                      parser.restart();
+                      if (CanCompileLayout(&parser)) {
+                          parser.restart();
+                          const std::string layout_name =
+                                  startop::util::FindLayoutNameFromFilename(layout_path);
+                          ResXmlVisitorAdapter adapter{&parser};
+                          switch (target) {
+                              case CompilationTarget::kDex: {
+                                  methods.push_back(compiled_view.CreateMethod(
+                                          layout_name,
+                                          dex::Prototype{dex::TypeDescriptor::FromClassname(
+                                                                 "android.view.View"),
+                                                         dex::TypeDescriptor::FromClassname(
+                                                                 "android.content.Context"),
+                                                         dex::TypeDescriptor::Int()}));
+                                  DexViewBuilder builder(&methods.back());
+                                  builder.Start();
+                                  LayoutCompilerVisitor visitor{&builder};
+                                  adapter.Accept(&visitor);
+                                  builder.Finish();
+                                  methods.back().Encode();
+                                  break;
+                              }
+                              case CompilationTarget::kJavaLanguage: {
+                                  JavaLangViewBuilder builder{package_name, layout_name,
+                                                              target_out};
+                                  builder.Start();
+                                  LayoutCompilerVisitor visitor{&builder};
+                                  adapter.Accept(&visitor);
+                                  builder.Finish();
+                                  break;
+                              }
+                          }
+                      }
+                  });
+      }
   });
 
   if (target == CompilationTarget::kDex) {
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index f848c40..a9cdf7e 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -210,6 +210,15 @@
     }
 
     /**
+     * Returns the userHandle of the current process, if called from a system app,
+     * otherwise it returns the caller's userHandle
+     * @return userHandle of the caller.
+     */
+    private static UserHandle getIncomingUserHandle() {
+        return UserHandle.of(getIncomingUserId());
+    }
+
+    /**
      * Returns the list of available SMS apps defined as apps that are registered for both the
      * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
      * receivers are enabled)
@@ -951,24 +960,28 @@
      */
     @UnsupportedAppUsage
     public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
-        return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+        return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
     }
 
     /**
      * Gets the default SMS application on a given user
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured.
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver SMS messages to
      */
-    @VisibleForTesting
     public static ComponentName getDefaultSmsApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
                         smsApplicationData.mSmsReceiverClass);
@@ -987,23 +1000,28 @@
      */
     @UnsupportedAppUsage
     public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
-        return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+        return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
     }
 
     /**
      * Gets the default MMS application on a given user
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured.
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver MMS messages to.
      */
     public static ComponentName getDefaultMmsApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
                         smsApplicationData.mMmsReceiverClass);
@@ -1024,23 +1042,28 @@
     public static ComponentName getDefaultRespondViaMessageApplication(Context context,
             boolean updateIfNeeded) {
         return getDefaultRespondViaMessageApplicationAsUser(context, updateIfNeeded,
-                getIncomingUserId());
+                getIncomingUserHandle());
     }
 
     /**
      * Gets the default Respond Via Message application on a given user
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to direct Respond Via Message intent to
      */
     public static ComponentName getDefaultRespondViaMessageApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
                         smsApplicationData.mRespondViaMessageClass);
@@ -1062,6 +1085,7 @@
     public static ComponentName getDefaultSendToApplication(Context context,
             boolean updateIfNeeded) {
         int userId = getIncomingUserId();
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1087,7 +1111,7 @@
     public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
             Context context, boolean updateIfNeeded) {
         return getDefaultExternalTelephonyProviderChangedApplicationAsUser(context, updateIfNeeded,
-                getIncomingUserId());
+                getIncomingUserHandle());
     }
 
     /**
@@ -1095,16 +1119,21 @@
      * MmsProvider on a given user.
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver change intents to.
      */
     public static ComponentName getDefaultExternalTelephonyProviderChangedApplicationAsUser(
-            Context context, boolean updateIfNeeded, int userId) {
+            Context context, boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null
                     && smsApplicationData.mProviderChangedReceiverClass != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
@@ -1124,23 +1153,28 @@
      */
     public static ComponentName getDefaultSimFullApplication(
             Context context, boolean updateIfNeeded) {
-        return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+        return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
     }
 
     /**
      * Gets the default application that handles sim full event on a given user.
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver change intents to
      */
     public static ComponentName getDefaultSimFullApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null
                     && smsApplicationData.mSimFullReceiverClass != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
@@ -1153,19 +1187,35 @@
     }
 
     /**
-     * Returns whether need to wrgetIncomingUserIdite the SMS message to SMS database for this
-     * package.
+     * Returns whether it is required to write the SMS message to SMS database for this package.
+     *
+     * @param packageName the name of the package to be checked
+     * @param context context from the calling app
+     * @return true if it is required to write SMS message to SMS database for this package.
+     *
      * <p>
      * Caller must pass in the correct user context if calling from a singleton service.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
-        return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserId());
+        return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserHandle());
     }
 
+    /**
+     * Returns whether it is required to write the SMS message to SMS database for this package.
+     *
+     * @param packageName the name of the package to be checked
+     * @param context context from the calling app
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
+     * @return true if it is required to write SMS message to SMS database for this package.
+     *
+     * <p>
+     * Caller must pass in the correct user context if calling from a singleton service.
+     */
     public static boolean shouldWriteMessageForPackageAsUser(String packageName, Context context,
-            int userId) {
-        return !isDefaultSmsApplicationAsUser(context, packageName, userId);
+            @Nullable UserHandle userHandle) {
+        return !isDefaultSmsApplicationAsUser(context, packageName, userHandle);
     }
 
     /**
@@ -1177,7 +1227,7 @@
      */
     @UnsupportedAppUsage
     public static boolean isDefaultSmsApplication(Context context, String packageName) {
-        return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserId());
+        return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserHandle());
     }
 
     /**
@@ -1185,16 +1235,22 @@
      *
      * @param context context from the calling app
      * @param packageName the name of the package to be checked
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return true if the package is default sms app or bluetooth
      */
     public static boolean isDefaultSmsApplicationAsUser(Context context, String packageName,
-            int userId) {
+            @Nullable UserHandle userHandle) {
         if (packageName == null) {
             return false;
         }
+
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         ComponentName component = getDefaultSmsApplicationAsUser(context, false,
-                userId);
+                userHandle);
         if (component == null) {
             return false;
         }
@@ -1222,7 +1278,7 @@
      */
     @UnsupportedAppUsage
     public static boolean isDefaultMmsApplication(Context context, String packageName) {
-        return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserId());
+        return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserHandle());
     }
 
     /**
@@ -1230,17 +1286,22 @@
      *
      * @param context context from the calling app
      * @param packageName the name of the package to be checked
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return true if the package is default mms app or bluetooth
      */
     public static boolean isDefaultMmsApplicationAsUser(Context context, String packageName,
-            int userId) {
+            @Nullable UserHandle userHandle) {
         if (packageName == null) {
             return false;
         }
 
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         ComponentName component = getDefaultMmsApplicationAsUser(context, false,
-                userId);
+                userHandle);
         if (component == null) {
             return false;
         }
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 76d2b7d..3dc7111 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -27,6 +27,8 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import java.io.PrintWriter;
@@ -212,4 +214,30 @@
                 return "UNKNOWN(" + mobileDataPolicy + ")";
         }
     }
-}
+
+    /**
+     * Utility method to get user handle associated with this subscription.
+     *
+     * This method should be used internally as it returns null instead of throwing
+     * IllegalArgumentException or IllegalStateException.
+     *
+     * @param context Context object
+     * @param subId the subId of the subscription.
+     * @return userHandle associated with this subscription
+     * or {@code null} if:
+     * 1. subscription is not associated with any user
+     * 2. subId is invalid.
+     * 3. subscription service is not available.
+     *
+     * @throws SecurityException if the caller doesn't have permissions required.
+     */
+    @Nullable
+    public static UserHandle getSubscriptionUserHandle(Context context, int subId) {
+        UserHandle userHandle = null;
+        SubscriptionManager subManager =  context.getSystemService(SubscriptionManager.class);
+        if ((subManager != null) && (SubscriptionManager.isValidSubscriptionId(subId))) {
+            userHandle = subManager.getSubscriptionUserHandle(subId);
+        }
+        return userHandle;
+    }
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ed96a9b..22cd31a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1996,6 +1996,15 @@
             "nr_advanced_threshold_bandwidth_khz_int";
 
     /**
+     * Indicating whether to include LTE cell bandwidths when determining whether the aggregated
+     * cell bandwidth meets the required threshold for NR advanced.
+     *
+     * @see TelephonyDisplayInfo#OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+     */
+    public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL =
+            "include_lte_for_nr_advanced_threshold_bandwidth_bool";
+
+    /**
      * Boolean indicating if operator name should be shown in the status bar
      * @hide
      */
@@ -9577,6 +9586,7 @@
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
         sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
+        sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
         sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                 new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4ce2ca1..5c1d497 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1663,17 +1663,33 @@
     }
 
     /**
-     * @return List of all SubscriptionInfo records in database,
-     * include those that were inserted before, maybe empty but not null.
+     * Get all subscription info records from SIMs that are inserted now or were inserted before.
+     *
+     * <p>
+     * If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
+     * {@link SubscriptionInfo#getNumber()} will return empty string.
+     * If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
+     * {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
+     * empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
+     *
+     * <p>
+     * The carrier app will always have full {@link SubscriptionInfo} for the subscriptions
+     * that it has carrier privilege.
+     *
+     * @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
+     * inserted before. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
+     * {@link SubscriptionInfo#getSubscriptionId()}.
+     *
      * @hide
      */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
+    })
     @NonNull
-    @UnsupportedAppUsage
     public List<SubscriptionInfo> getAllSubscriptionInfoList() {
-        if (VDBG) logd("[getAllSubscriptionInfoList]+");
-
         List<SubscriptionInfo> result = null;
-
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
@@ -3424,7 +3440,6 @@
 
     /**
      * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
-     * See {@link #createSubscriptionGroup(List)} for more details.
      *
      * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
      * or carrier privilege permission on the subscription.
@@ -4125,6 +4140,26 @@
     }
 
     /**
+     * Convert phone number source to string.
+     *
+     * @param source The phone name source.
+     *
+     * @return The phone name source in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String phoneNumberSourceToString(@PhoneNumberSource int source) {
+        switch (source) {
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC: return "UICC";
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER: return "CARRIER";
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS: return "IMS";
+            default:
+                return "UNKNOWN(" + source + ")";
+        }
+    }
+
+    /**
      * Convert display name source to string.
      *
      * @param source The display name source.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d3ddb1b..3024b89 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -71,6 +71,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.carrier.CarrierIdentifier;
@@ -11934,8 +11935,9 @@
     }
 
     /**
-     * Gets the default Respond Via Message application, updating the cache if there is no
-     * respond-via-message application currently configured.
+     * Get the component name of the default app to direct respond-via-message intent for the
+     * user associated with this subscription, update the cache if there is no respond-via-message
+     * application currently configured for this user.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
      * @hide
@@ -11944,11 +11946,20 @@
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public @Nullable ComponentName getAndUpdateDefaultRespondViaMessageApplication() {
-        return SmsApplication.getDefaultRespondViaMessageApplication(mContext, true);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getDefaultRespondViaMessageApplication(getSubId(), true);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in getAndUpdateDefaultRespondViaMessageApplication: " + e);
+        }
+        return null;
     }
 
     /**
-     * Gets the default Respond Via Message application.
+     * Get the component name of the default app to direct respond-via-message intent for the
+     * user associated with this subscription.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
      * @hide
@@ -11957,7 +11968,15 @@
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public @Nullable ComponentName getDefaultRespondViaMessageApplication() {
-        return SmsApplication.getDefaultRespondViaMessageApplication(mContext, false);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getDefaultRespondViaMessageApplication(getSubId(), false);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in getDefaultRespondViaMessageApplication: " + e);
+        }
+        return null;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index abf4cde..616ea50 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
@@ -2618,4 +2619,14 @@
      * @hide
      */
     boolean isRemovableEsimDefaultEuicc(String callingPackage);
+
+     /**
+      * Get the component name of the default app to direct respond-via-message intent for the
+      * user associated with this subscription, update the cache if there is no respond-via-message
+      * application currently configured for this user.
+      * @return component name of the app and class to direct Respond Via Message intent to, or
+      * {@code null} if the functionality is not supported.
+      * @hide
+      */
+      ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 0c14dba..a1257e3 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,8 @@
     int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
     int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
     int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+    int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+    int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -620,4 +622,5 @@
     int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107;
     int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
     int RIL_UNSOL_NOTIFY_ANBR = 1109;
+    int RIL_UNSOL_ON_NETWORK_INITIATED_LOCATION_RESULT = 1110;
 }
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index 5430dee..cfebf34 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -19,8 +19,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.UiAutomation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
@@ -96,7 +99,12 @@
     }
 
     @Before
-    public void primeEventLog() {
+    public void setup() {
+        assumeFalse(sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+        primeEventLog();
+    }
+
+    private void primeEventLog() {
         // Force a round trip to logd to make sure everything is up to date.
         // Without this the first test passes and others don't - we don't see new events in the
         // log. The exact reason is unclear.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 8a1e1fa..3f6a75d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -172,4 +172,17 @@
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
     }
+
+    open fun cujCompleted() {
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        statusBarLayerIsVisibleAtStartAndEnd()
+        statusBarLayerPositionAtStartAndEnd()
+        statusBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
new file mode 100644
index 0000000..945de33
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "ironwood-postsubmit": [
+    {
+      "name": "FlickerTests",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index b9c875a..ef42766 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -101,6 +102,16 @@
         testSpec.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppLayerIsAlwaysVisible()
+        imeAppWindowIsAlwaysVisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1dc3ca5..c92fce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -100,6 +101,17 @@
         testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppWindowBecomesInvisible()
+        imeWindowBecomesInvisible()
+        imeLayerBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index a6bd791..7d7953b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -79,6 +80,14 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeLayerBecomesInvisible()
+        imeWindowBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index b43efea..9919d87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -50,6 +51,15 @@
         }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeWindowBecomesVisible()
+        appWindowAlwaysVisibleOnTop()
+        layerAlwaysVisible()
+    }
+
     @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
 
     @Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1973ec0..ad14d0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -125,6 +126,14 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        focusChanges()
+        rotationLayerAppearsAndVanishes()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4faeb24..8e3fd40 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -73,4 +73,10 @@
             }
         }
     }
+
+    override fun cujCompleted() {
+        super.cujCompleted()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a08db29..d0d4122 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.WindowManager
@@ -204,6 +205,31 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        if (!testSpec.isTablet) {
+            // not yet tablet compatible
+            appLayerRotates()
+            appLayerAlwaysVisible()
+        }
+
+        appWindowFullScreen()
+        appWindowSeamlessRotation()
+        focusDoesNotChange()
+        statusBarLayerIsAlwaysInvisible()
+        statusBarWindowIsAlwaysInvisible()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         private val FlickerTestParameter.starveUiThread
             get() =
diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp
index a9fbfd9..81ec265 100644
--- a/tests/TelephonyCommonTests/Android.bp
+++ b/tests/TelephonyCommonTests/Android.bp
@@ -47,7 +47,7 @@
 
     // Uncomment this and comment out the jarjar rule if you want to attach a debugger and step
     // through the renamed classes.
-    // platform_apis: true,
+    platform_apis: true,
 
     libs: [
         "android.test.runner",
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 7a2af72..adefac6 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -44,6 +44,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -75,9 +76,12 @@
 public class SmsApplicationTest {
     private static final ComponentName TEST_COMPONENT_NAME =
             ComponentName.unflattenFromString("com.android.test/.TestSmsApp");
+    public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services";
     private static final String MMS_RECEIVER_NAME = "TestMmsReceiver";
     private static final String RESPOND_VIA_SMS_NAME = "TestRespondViaSmsHandler";
     private static final String SEND_TO_NAME = "TestSendTo";
+    private static final String EXTERNAL_PROVIDER_CHANGE_NAME = "TestExternalProviderChangeHandler";
+    private static final String SIM_FULL_NAME = "TestSimFullHandler";
     private static final int SMS_APP_UID = 10001;
 
     private static final int FAKE_PHONE_UID = 10002;
@@ -102,6 +106,7 @@
     }).collect(Collectors.toSet());
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private RoleManager mRoleManager;
     @Mock private PackageManager mPackageManager;
@@ -118,6 +123,9 @@
         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager);
         when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getString(eq(com.android.internal.R.string.config_systemBluetoothStack)))
+                .thenReturn(BLUETOOTH_PACKAGE_NAME);
 
         doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
                 .when(mPackageManager)
@@ -146,24 +154,46 @@
         }
     }
 
+
     @Test
-    public void testGetDefaultSmsApplication() {
+    public void testGetDefaultSmsApplicationAsUser() {
         assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, 0));
+                SmsApplication.getDefaultSmsApplicationAsUser(mContext, false,
+                        UserHandle.SYSTEM));
+    }
+
+
+    @Test
+    public void testGetDefaultMmsApplicationAsUser() {
+        ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
+                        false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(MMS_RECEIVER_NAME, componentName.getClassName());
     }
 
     @Test
-    public void testGetDefaultMmsApplication() {
-        assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultMmsApplicationAsUser(mContext, false,
-                        UserHandle.USER_SYSTEM));
+    public void testGetDefaultExternalTelephonyProviderChangedApplicationAsUser() {
+        ComponentName componentName = SmsApplication
+                .getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
+                        false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(EXTERNAL_PROVIDER_CHANGE_NAME, componentName.getClassName());
     }
 
     @Test
-    public void testGetDefaultExternalTelephonyProviderChangedApplication() {
-        assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
-                        false, UserHandle.USER_SYSTEM));
+    public void testGetDefaultRespondViaMessageApplicationAsUserAsUser() {
+        ComponentName componentName = SmsApplication.getDefaultRespondViaMessageApplicationAsUser(
+                mContext, false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(RESPOND_VIA_SMS_NAME, componentName.getClassName());
+    }
+
+    @Test
+    public void testGetDefaultSimFullApplicationAsUser() {
+        ComponentName componentName = SmsApplication.getDefaultSimFullApplicationAsUser(mContext,
+                false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(SIM_FULL_NAME, componentName.getClassName());
     }
 
     @Test
@@ -174,7 +204,8 @@
         setupPackageInfosForCoreApps();
 
         assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, 0));
+                SmsApplication.getDefaultSmsApplicationAsUser(mContext, true,
+                        UserHandle.SYSTEM));
         verify(mAppOpsManager, atLeastOnce()).setUidMode(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID,
                 AppOpsManager.MODE_ALLOWED);
     }
@@ -251,6 +282,10 @@
                 return Collections.singletonList(makeRespondViaMessageResolveInfo());
             case Intent.ACTION_SENDTO:
                 return Collections.singletonList(makeSendToResolveInfo());
+            case Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE:
+                return Collections.singletonList(makeExternalProviderChangeResolveInfo());
+            case Telephony.Sms.Intents.SIM_FULL_ACTION:
+                return Collections.singletonList(makeSimFullResolveInfo());
         }
         return Collections.emptyList();
     }
@@ -308,4 +343,26 @@
         info.activityInfo = activityInfo;
         return info;
     }
+
+    private ResolveInfo makeExternalProviderChangeResolveInfo() {
+        ResolveInfo info = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+
+        activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+        activityInfo.name = EXTERNAL_PROVIDER_CHANGE_NAME;
+
+        info.activityInfo = activityInfo;
+        return info;
+    }
+
+    private ResolveInfo makeSimFullResolveInfo() {
+        ResolveInfo info = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+
+        activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+        activityInfo.name = SIM_FULL_NAME;
+
+        info.activityInfo = activityInfo;
+        return info;
+    }
 }
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
new file mode 100644
index 0000000..a62103e
--- /dev/null
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.telephony.tests;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class TelephonyUtilsTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    // Mocked classes
+    @Mock
+    private Context mContext;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+
+    @Before
+    public void setup() {
+        doReturn(mSubscriptionManager).when(mContext)
+                .getSystemService(eq(SubscriptionManager.class));
+    }
+
+
+    @Test
+    public void getSubscriptionUserHandle_subId_invalid() {
+        int invalidSubId = -10;
+        doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(eq(invalidSubId));
+
+        TelephonyUtils.getSubscriptionUserHandle(mContext, invalidSubId);
+
+        // getSubscriptionUserHandle should not be called if subID is inactive.
+        verify(mSubscriptionManager, never()).getSubscriptionUserHandle(eq(invalidSubId));
+    }
+
+    @Test
+    public void getSubscriptionUserHandle_subId_valid() {
+        int activeSubId = 1;
+        doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(activeSubId));
+
+        TelephonyUtils.getSubscriptionUserHandle(mContext, activeSubId);
+
+        // getSubscriptionUserHandle should be called if subID is active.
+        verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId));
+    }
+}
+
+
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index d133f6f..e2099e6 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -24,12 +24,15 @@
 
 import android.content.Context;
 import android.hardware.usb.UsbManager;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Unit tests lib for {@link android.hardware.usb.UsbManager}.
  */
@@ -42,6 +45,11 @@
     private UsbManager mUsbManagerMock;
     @Mock private android.hardware.usb.IUsbManager mMockUsbService;
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     public UsbManagerTestLib(Context context) {
         MockitoAnnotations.initMocks(this);
         mContext = context;
@@ -82,10 +90,11 @@
     }
 
     private void testSetCurrentFunctionsMock_Matched(long functions) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
             setCurrentFunctions(functions);
 
-            verify(mMockUsbService).setCurrentFunctions(eq(functions));
+            verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
         } catch (RemoteException remEx) {
             Log.w(TAG, "RemoteException");
         }
@@ -106,9 +115,10 @@
     }
 
     public void testSetCurrentFunctionsEx(long functions) throws Exception {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         setCurrentFunctions(functions);
 
-        verify(mMockUsbService).setCurrentFunctions(eq(functions));
+        verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
     }
 
     public void testGetCurrentFunctions_shouldMatched() {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 86bcb72..4103ca7 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -98,7 +98,7 @@
         }
 
         @Override
-        protected void setEnabledFunctions(long functions, boolean force) {
+        protected void setEnabledFunctions(long functions, boolean force, int operationId) {
             mCurrentFunctions = functions;
         }
 
@@ -134,6 +134,20 @@
         protected void sendStickyBroadcast(Intent intent) {
             mBroadcastedIntent = intent;
         }
+
+        @Override
+        public void handlerInitDone(int operationId) {
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed){
+        }
+
     }
 
     @Before
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 9b9cde2..6b1fd9f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -72,7 +72,7 @@
   }
 }
 
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path,
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(StringPiece path,
                                                       android::IDiagnostics* diag) {
   android::Source source(path);
   std::string error;
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index a4aff3f..4cd7eae 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -45,7 +45,7 @@
   virtual ~LoadedApk() = default;
 
   // Loads both binary and proto APKs from disk.
-  static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+  static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
                                                     android::IDiagnostics* diag);
 
   // Loads a proto APK from the given file collection.
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 0b49052..0b08c32 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -36,7 +36,7 @@
    * We must know which references to mangle, and which to keep (android vs.
    * com.android.support).
    */
-  std::set<std::string> packages_to_mangle;
+  std::set<std::string, std::less<>> packages_to_mangle;
 };
 
 class NameMangler {
@@ -54,7 +54,7 @@
                         mangled_entry_name);
   }
 
-  bool ShouldMangle(const std::string& package) const {
+  bool ShouldMangle(std::string_view package) const {
     if (package.empty() || policy_.target_package_name == package) {
       return false;
     }
@@ -68,8 +68,8 @@
    * The mangled name should contain symbols that are illegal to define in XML,
    * so that there will never be name mangling collisions.
    */
-  static std::string MangleEntry(const std::string& package, const std::string& name) {
-    return package + "$" + name;
+  static std::string MangleEntry(std::string_view package, std::string_view name) {
+    return (std::string(package) += '$') += name;
   }
 
   /**
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index df8c3b9..cfcb2bb 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -138,11 +138,11 @@
   return {to_string(t), t};
 }
 
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s) {
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s) {
   auto dot = std::find(s.begin(), s.end(), '.');
   const ResourceType* parsedType;
   if (dot != s.end() && dot != std::prev(s.end())) {
-    parsedType = ParseResourceType(s.substr(s.begin(), dot));
+    parsedType = ParseResourceType(android::StringPiece(s.begin(), dot - s.begin()));
   } else {
     parsedType = ParseResourceType(s);
   }
@@ -152,7 +152,7 @@
   return ResourceNamedTypeRef(s, *parsedType);
 }
 
-const ResourceType* ParseResourceType(const StringPiece& str) {
+const ResourceType* ParseResourceType(StringPiece str) {
   auto iter = sResourceTypeMap.find(str);
   if (iter == std::end(sResourceTypeMap)) {
     return nullptr;
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 9cfaf47..7ba3277 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -74,7 +74,7 @@
 /**
  * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid.
  */
-const ResourceType* ParseResourceType(const android::StringPiece& str);
+const ResourceType* ParseResourceType(android::StringPiece str);
 
 /**
  * Pair of type name as in ResourceTable and actual resource type.
@@ -87,7 +87,7 @@
   ResourceType type = ResourceType::kRaw;
 
   ResourceNamedType() = default;
-  ResourceNamedType(const android::StringPiece& n, ResourceType t);
+  ResourceNamedType(android::StringPiece n, ResourceType t);
 
   int compare(const ResourceNamedType& other) const;
 
@@ -108,19 +108,19 @@
   ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default;
   ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default;
   ResourceNamedTypeRef(const ResourceNamedType& rhs);  // NOLINT(google-explicit-constructor)
-  ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t);
+  ResourceNamedTypeRef(android::StringPiece n, ResourceType t);
   ResourceNamedTypeRef& operator=(const ResourceNamedTypeRef& rhs) = default;
   ResourceNamedTypeRef& operator=(ResourceNamedTypeRef&& rhs) = default;
   ResourceNamedTypeRef& operator=(const ResourceNamedType& rhs);
 
   ResourceNamedType ToResourceNamedType() const;
 
-  std::string to_string() const;
+  std::string_view to_string() const;
 };
 
 ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t);
 
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s);
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s);
 
 /**
  * A resource's name. This can uniquely identify
@@ -132,9 +132,8 @@
   std::string entry;
 
   ResourceName() = default;
-  ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-               const android::StringPiece& e);
-  ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+  ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+  ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e);
 
   int compare(const ResourceName& other) const;
 
@@ -157,9 +156,8 @@
   ResourceNameRef(const ResourceNameRef&) = default;
   ResourceNameRef(ResourceNameRef&&) = default;
   ResourceNameRef(const ResourceName& rhs);  // NOLINT(google-explicit-constructor)
-  ResourceNameRef(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-                  const android::StringPiece& e);
-  ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+  ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+  ResourceNameRef(android::StringPiece p, ResourceType t, android::StringPiece e);
   ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
   ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
   ResourceNameRef& operator=(const ResourceName& rhs);
@@ -346,8 +344,8 @@
 //
 // ResourceNamedType implementation.
 //
-inline ResourceNamedType::ResourceNamedType(const android::StringPiece& n, ResourceType t)
-    : name(n.to_string()), type(t) {
+inline ResourceNamedType::ResourceNamedType(android::StringPiece n, ResourceType t)
+    : name(n), type(t) {
 }
 
 inline int ResourceNamedType::compare(const ResourceNamedType& other) const {
@@ -380,7 +378,7 @@
 //
 // ResourceNamedTypeRef implementation.
 //
-inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t)
+inline ResourceNamedTypeRef::ResourceNamedTypeRef(android::StringPiece n, ResourceType t)
     : name(n), type(t) {
 }
 
@@ -398,8 +396,8 @@
   return ResourceNamedType(name, type);
 }
 
-inline std::string ResourceNamedTypeRef::to_string() const {
-  return name.to_string();
+inline std::string_view ResourceNamedTypeRef::to_string() const {
+  return name;
 }
 
 inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) {
@@ -422,13 +420,12 @@
 // ResourceName implementation.
 //
 
-inline ResourceName::ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-                                  const android::StringPiece& e)
-    : package(p.to_string()), type(t.ToResourceNamedType()), entry(e.to_string()) {
+inline ResourceName::ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t,
+                                  android::StringPiece e)
+    : package(p), type(t.ToResourceNamedType()), entry(e) {
 }
 
-inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t,
-                                  const android::StringPiece& e)
+inline ResourceName::ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e)
     : ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) {
 }
 
@@ -471,14 +468,13 @@
 inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
     : package(rhs.package), type(rhs.type), entry(rhs.entry) {}
 
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p,
-                                        const ResourceNamedTypeRef& t,
-                                        const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t,
+                                        android::StringPiece e)
     : package(p), type(t), entry(e) {
 }
 
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t,
-                                        const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, ResourceType t,
+                                        android::StringPiece e)
     : ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) {
 }
 
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 19fd306..fa9a98f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -50,11 +50,11 @@
 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
 
 // Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
-static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
+static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) {
   return ns.empty() && (name == "skip" || name == "eat-comment");
 }
 
-static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
+static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) {
   if (piece == "reference") {
     return android::ResTable_map::TYPE_REFERENCE;
   } else if (piece == "string") {
@@ -75,7 +75,7 @@
   return 0;
 }
 
-static uint32_t ParseFormatType(const StringPiece& piece) {
+static uint32_t ParseFormatType(StringPiece piece) {
   if (piece == "enum") {
     return android::ResTable_map::TYPE_ENUM;
   } else if (piece == "flags") {
@@ -84,9 +84,9 @@
   return ParseFormatTypeNoEnumsOrFlags(piece);
 }
 
-static uint32_t ParseFormatAttribute(const StringPiece& str) {
+static uint32_t ParseFormatAttribute(StringPiece str) {
   uint32_t mask = 0;
-  for (const StringPiece& part : util::Tokenize(str, '|')) {
+  for (StringPiece part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
     uint32_t type = ParseFormatType(trimmed_part);
     if (type == 0) {
@@ -122,7 +122,7 @@
   StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
   if (trimmed_comment.size() != res->comment.size()) {
     // Only if there was a change do we re-assign.
-    res->comment = trimmed_comment.to_string();
+    res->comment = std::string(trimmed_comment);
   }
 
   NewResourceBuilder res_builder(res->name);
@@ -362,7 +362,7 @@
       // Trim leading whitespace.
       StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
       if (trimmed.size() != first_segment->data.size()) {
-        first_segment->data = trimmed.to_string();
+        first_segment->data = std::string(trimmed);
       }
     }
 
@@ -370,7 +370,7 @@
       // Trim trailing whitespace.
       StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
       if (trimmed.size() != last_segment->data.size()) {
-        last_segment->data = trimmed.to_string();
+        last_segment->data = std::string(trimmed);
       }
     }
   }
@@ -466,7 +466,7 @@
 
     // Extract the product name if it exists.
     if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
-      parsed_resource.product = maybe_product.value().to_string();
+      parsed_resource.product = std::string(maybe_product.value());
     }
 
     // Parse the resource regardless of product.
@@ -559,7 +559,7 @@
 
     // Items have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = maybe_type.value().to_string();
+      resource_type = std::string(maybe_type.value());
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<item> must have a 'type' attribute");
@@ -582,7 +582,7 @@
 
     // Bags have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = maybe_type.value().to_string();
+      resource_type = std::string(maybe_type.value());
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<bag> must have a 'type' attribute");
@@ -603,7 +603,7 @@
 
     out_resource->name.type =
         ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType();
-    out_resource->name.entry = maybe_name.value().to_string();
+    out_resource->name.entry = std::string(maybe_name.value());
 
     // Ids either represent a unique resource id or reference another resource id
     auto item = ParseItem(parser, out_resource, resource_format);
@@ -640,7 +640,7 @@
 
     out_resource->name.type =
         ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType();
-    out_resource->name.entry = maybe_name.value().to_string();
+    out_resource->name.entry = std::string(maybe_name.value());
     return ParseMacro(parser, out_resource);
   }
 
@@ -657,7 +657,7 @@
 
       out_resource->name.type =
           ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType();
-      out_resource->name.entry = maybe_name.value().to_string();
+      out_resource->name.entry = std::string(maybe_name.value());
 
       // Only use the implied format of the type when there is no explicit format.
       if (resource_format == 0u) {
@@ -684,7 +684,7 @@
           return false;
         }
 
-        out_resource->name.entry = maybe_name.value().to_string();
+        out_resource->name.entry = std::string(maybe_name.value());
       }
 
       // Call the associated parse method. The type will be filled in by the
@@ -708,7 +708,7 @@
       }
 
       out_resource->name.type = parsed_type->ToResourceNamedType();
-      out_resource->name.entry = maybe_name.value().to_string();
+      out_resource->name.entry = std::string(maybe_name.value());
       out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
       if (!out_resource->value) {
         diag_->Error(android::DiagMessage(out_resource->source)
@@ -1005,7 +1005,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Skip text.
@@ -1045,7 +1045,7 @@
       }
 
       ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
-          .name = ResourceName{{}, parsed_type, maybe_name.value().to_string()},
+          .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())},
           .source = item_source,
           .comment = std::move(comment),
       });
@@ -1231,7 +1231,7 @@
 
       ParsedResource child_resource{};
       child_resource.name.type = type->ToResourceNamedType();
-      child_resource.name.entry = item_name.value().to_string();
+      child_resource.name.entry = std::string(item_name.value());
       child_resource.overlayable_item = overlayable_item;
       out_resource->child_resources.push_back(std::move(child_resource));
 
@@ -1246,7 +1246,7 @@
                      xml::FindNonEmptyAttribute(parser, "type")) {
         // Parse the polices separated by vertical bar characters to allow for specifying multiple
         // policies. Items within the policy tag will have the specified policy.
-        for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
+        for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
           StringPiece trimmed_part = util::TrimWhitespace(part);
           const auto policy = std::find_if(kPolicyStringToFlag.begin(),
                                            kPolicyStringToFlag.end(),
@@ -1377,7 +1377,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Skip text.
@@ -1457,7 +1457,7 @@
 }
 
 std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser,
-                                                                     const StringPiece& tag) {
+                                                                     StringPiece tag) {
   const android::Source source = source_.WithLine(parser->line_number());
 
   std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
@@ -1764,7 +1764,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Ignore text.
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 396ce97..012a056 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -122,7 +122,7 @@
   bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
   std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
-                                                       const android::StringPiece& tag);
+                                                       android::StringPiece tag);
   bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
   bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index fe7eb96..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -65,11 +65,11 @@
     context_ = test::ContextBuilder().Build();
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str) {
+  ::testing::AssertionResult TestParse(StringPiece str) {
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) {
+  ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
     ResourceParserOptions parserOptions;
     ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
                           parserOptions);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index cb48114..a3b0b45 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -49,21 +49,21 @@
 }
 
 template <typename T>
-bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
+bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) {
   return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
 template <typename T>
-bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) {
+bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) {
   return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
 }
 
 template <typename T>
 struct NameEqualRange {
-  bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const {
+  bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const {
     return less_than_struct_with_name<T>(lhs, rhs);
   }
-  bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const {
+  bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const {
     return greater_than_struct_with_name<T>(lhs, rhs);
   }
 };
@@ -78,7 +78,7 @@
 }
 
 template <typename T, typename Func, typename Elements>
-T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
+T* FindElementsRunAction(android::StringPiece name, Elements& entries, Func action) {
   const auto iter =
       std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>);
   const bool found = iter != entries.end() && name == (*iter)->name;
@@ -87,7 +87,7 @@
 
 struct ConfigKey {
   const ConfigDescription* config;
-  const StringPiece& product;
+  StringPiece product;
 };
 
 template <typename T>
@@ -104,12 +104,12 @@
 ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
 }
 
-ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const {
+ResourceTablePackage* ResourceTable::FindPackage(android::StringPiece name) const {
   return FindElementsRunAction<ResourceTablePackage>(
       name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
 }
 
-ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(android::StringPiece name) {
   return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) {
     return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get();
   });
@@ -139,18 +139,18 @@
   });
 }
 
-ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::CreateEntry(android::StringPiece name) {
   return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
     return entries.emplace(iter, new ResourceEntry(name))->get();
   });
 }
 
-ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const {
+ResourceEntry* ResourceTableType::FindEntry(android::StringPiece name) const {
   return FindElementsRunAction<ResourceEntry>(
       name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
 }
 
-ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::FindOrCreateEntry(android::StringPiece name) {
   return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
     return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get();
   });
@@ -183,7 +183,7 @@
 }
 
 ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
-                                                      const StringPiece& product) {
+                                                      StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
                                lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index f49ce81..bb286a8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -71,12 +71,11 @@
 
 struct Overlayable {
   Overlayable() = default;
-   Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
-       : name(name.to_string()), actor(actor.to_string()) {}
-   Overlayable(const android::StringPiece& name, const android::StringPiece& actor,
-               const android::Source& source)
-       : name(name.to_string()), actor(actor.to_string()), source(source) {
-   }
+  Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) {
+  }
+  Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source)
+      : name(name), actor(actor), source(source) {
+  }
 
   static const char* kActorScheme;
   std::string name;
@@ -105,8 +104,9 @@
   // The actual Value.
   std::unique_ptr<Value> value;
 
-  ResourceConfigValue(const android::ConfigDescription& config, const android::StringPiece& product)
-      : config(config), product(product.to_string()) {}
+  ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
+      : config(config), product(product) {
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
@@ -136,7 +136,8 @@
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
-  explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
+  explicit ResourceEntry(android::StringPiece name) : name(name) {
+  }
 
   ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                  android::StringPiece product = {});
@@ -144,7 +145,7 @@
                                        android::StringPiece product = {}) const;
 
   ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config,
-                                         const android::StringPiece& product);
+                                         android::StringPiece product);
   std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);
 
   template <typename Func>
@@ -180,9 +181,9 @@
       : named_type(type.ToResourceNamedType()) {
   }
 
-  ResourceEntry* CreateEntry(const android::StringPiece& name);
-  ResourceEntry* FindEntry(const android::StringPiece& name) const;
-  ResourceEntry* FindOrCreateEntry(const android::StringPiece& name);
+  ResourceEntry* CreateEntry(android::StringPiece name);
+  ResourceEntry* FindEntry(android::StringPiece name) const;
+  ResourceEntry* FindOrCreateEntry(android::StringPiece name);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
@@ -194,7 +195,7 @@
 
   std::vector<std::unique_ptr<ResourceTableType>> types;
 
-  explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) {
+  explicit ResourceTablePackage(android::StringPiece name) : name(name) {
   }
 
   ResourceTablePackage() = default;
@@ -319,8 +320,8 @@
   // Returns the package struct with the given name, or nullptr if such a package does not
   // exist. The empty string is a valid package and typically is used to represent the
   // 'current' package before it is known to the ResourceTable.
-  ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
-  ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
+  ResourceTablePackage* FindPackage(android::StringPiece name) const;
+  ResourceTablePackage* FindOrCreatePackage(android::StringPiece name);
 
   std::unique_ptr<ResourceTable> Clone() const;
 
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 0cf8473..54b98d1 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -187,7 +187,7 @@
 static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
                                                        const ResourceNameRef& name,
                                                        Visibility::Level level,
-                                                       const StringPiece& comment) {
+                                                       StringPiece comment) {
   std::optional<ResourceTable::SearchResult> result = table.FindResource(name);
   if (!result) {
     return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 41c7435..5a118a9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -109,8 +109,7 @@
   return name_out;
 }
 
-bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
-                       bool* out_private) {
+bool ParseResourceName(StringPiece str, ResourceNameRef* out_ref, bool* out_private) {
   if (str.empty()) {
     return false;
   }
@@ -151,8 +150,8 @@
   return true;
 }
 
-bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
-                    bool* out_create, bool* out_private) {
+bool ParseReference(StringPiece str, ResourceNameRef* out_ref, bool* out_create,
+                    bool* out_private) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str.empty()) {
     return false;
@@ -198,11 +197,11 @@
   return false;
 }
 
-bool IsReference(const StringPiece& str) {
+bool IsReference(StringPiece str) {
   return ParseReference(str, nullptr, nullptr, nullptr);
 }
 
-bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
+bool ParseAttributeReference(StringPiece str, ResourceNameRef* out_ref) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str.empty()) {
     return false;
@@ -235,7 +234,7 @@
   return false;
 }
 
-bool IsAttributeReference(const StringPiece& str) {
+bool IsAttributeReference(StringPiece str) {
   return ParseAttributeReference(str, nullptr);
 }
 
@@ -247,7 +246,7 @@
  * <[*]package>:[style/]<entry>
  * [[*]package:style/]<entry>
  */
-std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) {
+std::optional<Reference> ParseStyleParentReference(StringPiece str, std::string* out_error) {
   if (str.empty()) {
     return {};
   }
@@ -296,7 +295,7 @@
   return result;
 }
 
-std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) {
+std::optional<Reference> ParseXmlAttributeName(StringPiece str) {
   StringPiece trimmed_str = util::TrimWhitespace(str);
   const char* start = trimmed_str.data();
   const char* const end = start + trimmed_str.size();
@@ -325,8 +324,7 @@
   return std::optional<Reference>(std::move(ref));
 }
 
-std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
-                                             bool* out_create) {
+std::unique_ptr<Reference> TryParseReference(StringPiece str, bool* out_create) {
   ResourceNameRef ref;
   bool private_ref = false;
   if (ParseReference(str, &ref, out_create, &private_ref)) {
@@ -344,7 +342,7 @@
   return {};
 }
 
-std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) {
+std::unique_ptr<Item> TryParseNullOrEmpty(StringPiece str) {
   const StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "@null") {
     return MakeNull();
@@ -365,8 +363,7 @@
                                             android::Res_value::DATA_NULL_EMPTY);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
-                                                    const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   for (const Attribute::Symbol& symbol : enum_attr->symbols) {
     // Enum symbols are stored as @package:id/symbol resources,
@@ -382,8 +379,7 @@
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
-                                                    const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, StringPiece str) {
   android::Res_value flags = {};
   flags.dataType = android::Res_value::TYPE_INT_HEX;
   flags.data = 0u;
@@ -393,7 +389,7 @@
     return util::make_unique<BinaryPrimitive>(flags);
   }
 
-  for (const StringPiece& part : util::Tokenize(str, '|')) {
+  for (StringPiece part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
 
     bool flag_set = false;
@@ -429,7 +425,7 @@
   }
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseColor(StringPiece str) {
   StringPiece color_str(util::TrimWhitespace(str));
   const char* start = color_str.data();
   const size_t len = color_str.size();
@@ -484,7 +480,7 @@
                : util::make_unique<BinaryPrimitive>(value);
 }
 
-std::optional<bool> ParseBool(const StringPiece& str) {
+std::optional<bool> ParseBool(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
     return std::optional<bool>(true);
@@ -495,7 +491,7 @@
   return {};
 }
 
-std::optional<uint32_t> ParseInt(const StringPiece& str) {
+std::optional<uint32_t> ParseInt(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(str);
   android::Res_value value;
   if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -504,7 +500,7 @@
   return {};
 }
 
-std::optional<ResourceId> ParseResourceId(const StringPiece& str) {
+std::optional<ResourceId> ParseResourceId(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
 
   std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -520,7 +516,7 @@
   return {};
 }
 
-std::optional<int> ParseSdkVersion(const StringPiece& str) {
+std::optional<int> ParseSdkVersion(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
 
   std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -539,14 +535,14 @@
   const StringPiece::const_iterator begin = std::begin(trimmed_str);
   const StringPiece::const_iterator end = std::end(trimmed_str);
   const StringPiece::const_iterator codename_end = std::find(begin, end, '.');
-  entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end));
+  entry = GetDevelopmentSdkCodeNameVersion(StringPiece(begin, codename_end - begin));
   if (entry) {
     return entry.value();
   }
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseBool(StringPiece str) {
   if (std::optional<bool> maybe_result = ParseBool(str)) {
     const uint32_t data = maybe_result.value() ? 0xffffffffu : 0u;
     return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, data);
@@ -559,7 +555,7 @@
                                             val ? 0xffffffffu : 0u);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseInt(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
   android::Res_value value;
   if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -572,7 +568,7 @@
   return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFloat(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
   android::Res_value value;
   if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
@@ -623,7 +619,7 @@
 }
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const StringPiece& value, uint32_t type_mask,
+    StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
@@ -687,7 +683,7 @@
  * allows.
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const StringPiece& str, const Attribute* attr,
+    StringPiece str, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 22cf345..f30f4ac 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -38,7 +38,7 @@
  * `out_resource` set to the parsed resource name and `out_private` set to true
  * if a '*' prefix was present.
  */
-bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource,
+bool ParseResourceName(android::StringPiece str, ResourceNameRef* out_resource,
                        bool* out_private = nullptr);
 
 /*
@@ -49,27 +49,27 @@
  * If '+' was present in the reference, `out_create` is set to true.
  * If '*' was present in the reference, `out_private` is set to true.
  */
-bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference,
+bool ParseReference(android::StringPiece str, ResourceNameRef* out_reference,
                     bool* out_create = nullptr, bool* out_private = nullptr);
 
 /*
  * Returns true if the string is in the form of a resource reference
  * (@[+][package:]type/name).
  */
-bool IsReference(const android::StringPiece& str);
+bool IsReference(android::StringPiece str);
 
 /*
  * Returns true if the string was parsed as an attribute reference
  * (?[package:][type/]name),
  * with `out_reference` set to the parsed reference.
  */
-bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference);
+bool ParseAttributeReference(android::StringPiece str, ResourceNameRef* out_reference);
 
 /**
  * Returns true if the string is in the form of an attribute
  * reference(?[package:][type/]name).
  */
-bool IsAttributeReference(const android::StringPiece& str);
+bool IsAttributeReference(android::StringPiece str);
 
 /**
  * Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
@@ -85,22 +85,22 @@
  * Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
  * false, or False.
  */
-std::optional<bool> ParseBool(const android::StringPiece& str);
+std::optional<bool> ParseBool(android::StringPiece str);
 
 /**
  * Returns a uint32_t if the string is an integer.
  */
-std::optional<uint32_t> ParseInt(const android::StringPiece& str);
+std::optional<uint32_t> ParseInt(android::StringPiece str);
 
 /**
  * Returns an ID if it the string represented a valid ID.
  */
-std::optional<ResourceId> ParseResourceId(const android::StringPiece& str);
+std::optional<ResourceId> ParseResourceId(android::StringPiece str);
 
 /**
  * Parses an SDK version, which can be an integer, or a letter from A-Z.
  */
-std::optional<int> ParseSdkVersion(const android::StringPiece& str);
+std::optional<int> ParseSdkVersion(android::StringPiece str);
 
 /*
  * Returns a Reference, or None Maybe instance if the string `str` was parsed as
@@ -113,7 +113,7 @@
  * ?[package:]style/<entry> or
  * <package>:[style/]<entry>
  */
-std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str,
+std::optional<Reference> ParseStyleParentReference(android::StringPiece str,
                                                    std::string* out_error);
 
 /*
@@ -123,7 +123,7 @@
  *
  * package:entry
  */
-std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str);
+std::optional<Reference> ParseXmlAttributeName(android::StringPiece str);
 
 /*
  * Returns a Reference object if the string was parsed as a resource or
@@ -132,14 +132,13 @@
  * if
  * the '+' was present in the string.
  */
-std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str,
-                                             bool* out_create = nullptr);
+std::unique_ptr<Reference> TryParseReference(android::StringPiece str, bool* out_create = nullptr);
 
 /*
  * Returns a BinaryPrimitve object representing @null or @empty if the string
  * was parsed as one.
  */
-std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str);
+std::unique_ptr<Item> TryParseNullOrEmpty(android::StringPiece str);
 
 // Returns a Reference representing @null.
 // Due to runtime compatibility issues, this is encoded as a reference with ID 0.
@@ -154,13 +153,13 @@
  * Returns a BinaryPrimitve object representing a color if the string was parsed
  * as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing a boolean if the string was
  * parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(android::StringPiece str);
 
 // Returns a boolean BinaryPrimitive.
 std::unique_ptr<BinaryPrimitive> MakeBool(bool val);
@@ -169,7 +168,7 @@
  * Returns a BinaryPrimitve object representing an integer if the string was
  * parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(android::StringPiece str);
 
 // Returns an integer BinaryPrimitive.
 std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value);
@@ -178,21 +177,21 @@
  * Returns a BinaryPrimitve object representing a floating point number
  * (float, dimension, etc) if the string was parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing an enum symbol if the string was
  * parsed as one.
  */
 std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
-                                                    const android::StringPiece& str);
+                                                    android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing a flag symbol if the string was
  * parsed as one.
  */
 std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
-                                                    const android::StringPiece& str);
+                                                    android::StringPiece str);
 /*
  * Try to convert a string to an Item for the given attribute. The attribute
  * will
@@ -201,11 +200,11 @@
  * reference to an ID that must be created (@+id/foo).
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const android::StringPiece& value, const Attribute* attr,
+    android::StringPiece value, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const android::StringPiece& value, uint32_t type_mask,
+    android::StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index c4d54be..a5754e0 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -206,7 +206,7 @@
   PrettyPrintReferenceImpl(*this, true /*print_package*/, printer);
 }
 
-void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const {
+void Reference::PrettyPrint(StringPiece package, Printer* printer) const {
   const bool print_package = name ? package != name.value().package : true;
   PrettyPrintReferenceImpl(*this, print_package, printer);
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f5167a1..6f9dccb 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -83,8 +83,8 @@
     return comment_;
   }
 
-  void SetComment(const android::StringPiece& str) {
-    comment_ = str.to_string();
+  void SetComment(android::StringPiece str) {
+    comment_.assign(str);
   }
 
   void SetComment(std::string&& str) {
@@ -176,7 +176,7 @@
   void PrettyPrint(text::Printer* printer) const override;
 
   // Prints the reference without a package name if the package name matches the one given.
-  void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const;
+  void PrettyPrint(android::StringPiece package, text::Printer* printer) const;
 };
 
 bool operator<(const Reference&, const Reference&);
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 34e8edb..a7c5479 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -77,7 +77,7 @@
   return iter->second;
 }
 
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) {
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
   return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
              ? std::optional<ApiVersion>()
              : sDevelopmentSdkLevel;
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 0bd61c0..40bcef7 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -63,7 +63,7 @@
 };
 
 ApiVersion FindAttributeSdkLevel(const ResourceId& id);
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name);
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(android::StringPiece code_name);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp
index 697b110..3c0831c 100644
--- a/tools/aapt2/cmd/ApkInfo.cpp
+++ b/tools/aapt2/cmd/ApkInfo.cpp
@@ -64,7 +64,7 @@
     Usage(&std::cerr);
     return 1;
   }
-  const StringPiece& path = args[0];
+  StringPiece path = args[0];
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_);
   if (!apk) {
     return 1;
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index b1452fa..514651e 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -33,7 +33,7 @@
 
 namespace aapt {
 
-std::string GetSafePath(const StringPiece& arg) {
+std::string GetSafePath(StringPiece arg) {
 #ifdef _WIN32
   // If the path exceeds the maximum path length for Windows, encode the path using the
   // extended-length prefix
@@ -47,63 +47,62 @@
 
   return path8;
 #else
-  return arg.to_string();
+  return std::string(arg);
 #endif
 }
 
-void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description,
-                              std::string* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
+                              uint32_t flags) {
+  auto func = [value, flags](StringPiece arg) -> bool {
+    *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
 }
 
-void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+  auto func = [value, flags](StringPiece arg) -> bool {
+    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlag(StringPiece name, StringPiece description,
                               std::optional<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+  auto func = [value, flags](StringPiece arg) -> bool {
+    *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+  auto func = [value, flags](StringPiece arg) -> bool {
+    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::unordered_set<std::string>* value) {
-  auto func = [value](const StringPiece& arg) -> bool {
-    value->insert(arg.to_string());
+  auto func = [value](StringPiece arg) -> bool {
+    value->emplace(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description,
-                                bool* value) {
-  auto func = [value](const StringPiece& arg) -> bool {
+void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
+  auto func = [value](StringPiece arg) -> bool {
     *value = true;
     return true;
   };
@@ -120,8 +119,8 @@
   }
 }
 
-void Command::SetDescription(const StringPiece& description) {
-  description_ = description.to_string();
+void Command::SetDescription(StringPiece description) {
+  description_ = std::string(description);
 }
 
 void Command::Usage(std::ostream* out) {
@@ -183,7 +182,7 @@
   std::vector<std::string> file_args;
 
   for (size_t i = 0; i < args.size(); i++) {
-    const StringPiece& arg = args[i];
+    StringPiece arg = args[i];
     if (*(arg.data()) != '-') {
       // Continue parsing as the subcommand if the first argument matches one of the subcommands
       if (i == 0) {
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
index 8678cda..1416e98 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -30,13 +30,10 @@
 
 class Command {
  public:
-  explicit Command(const android::StringPiece& name)
-      : name_(name.to_string()), full_subcommand_name_(name.to_string()){};
+  explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){};
 
-  explicit Command(const android::StringPiece& name, const android::StringPiece& short_name)
-      : name_(name.to_string()),
-        short_name_(short_name.to_string()),
-        full_subcommand_name_(name.to_string()){};
+  explicit Command(android::StringPiece name, android::StringPiece short_name)
+      : name_(name), short_name_(short_name), full_subcommand_name_(name){};
 
   Command(Command&&) = default;
   Command& operator=(Command&&) = default;
@@ -52,30 +49,26 @@
     kPath = 1 << 0,
   };
 
-  void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
+  void AddRequiredFlag(android::StringPiece name, android::StringPiece description,
                        std::string* value, uint32_t flags = 0);
 
-  void AddRequiredFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description, std::vector<std::string>* value,
-                           uint32_t flags = 0);
+  void AddRequiredFlagList(android::StringPiece name, android::StringPiece description,
+                           std::vector<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
+  void AddOptionalFlag(android::StringPiece name, android::StringPiece description,
                        std::optional<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description, std::vector<std::string>* value,
-                           uint32_t flags = 0);
+  void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
+                           std::vector<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description,
+  void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
                            std::unordered_set<std::string>* value);
 
-  void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
-                         bool* value);
+  void AddOptionalSwitch(android::StringPiece name, android::StringPiece description, bool* value);
 
   void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false);
 
-  void SetDescription(const android::StringPiece& name);
+  void SetDescription(android::StringPiece name);
 
   // Prints the help menu of the command.
   void Usage(std::ostream* out);
@@ -90,17 +83,21 @@
 
  private:
   struct Flag {
-    explicit Flag(const android::StringPiece& name, const android::StringPiece& description,
+    explicit Flag(android::StringPiece name, android::StringPiece description,
                   const bool is_required, const size_t num_args,
-                  std::function<bool(const android::StringPiece& value)>&& action)
-        : name(name.to_string()), description(description.to_string()), is_required(is_required),
-          num_args(num_args), action(std::move(action)) {}
+                  std::function<bool(android::StringPiece value)>&& action)
+        : name(name),
+          description(description),
+          is_required(is_required),
+          num_args(num_args),
+          action(std::move(action)) {
+    }
 
     const std::string name;
     const std::string description;
     const bool is_required;
     const size_t num_args;
-    const std::function<bool(const android::StringPiece& value)> action;
+    const std::function<bool(android::StringPiece value)> action;
     bool found = false;
   };
 
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 0409f73..03f9715 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -125,8 +125,12 @@
   const android::Source res_path =
       options.source_path ? StringPiece(options.source_path.value()) : StringPiece(path);
 
-  return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
-                          extension.to_string(), config_str.to_string(), config};
+  return ResourcePathData{res_path,
+                          std::string(dir_str),
+                          std::string(name),
+                          std::string(extension),
+                          std::string(config_str),
+                          config};
 }
 
 static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
@@ -279,7 +283,7 @@
   return true;
 }
 
-static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file,
                                        io::KnownSizeInputStream* in, IArchiveWriter* writer,
                                        android::IDiagnostics* diag) {
   TRACE_CALL();
@@ -311,7 +315,7 @@
   return true;
 }
 
-static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+static bool FlattenXmlToOutStream(StringPiece output_path, const xml::XmlResource& xmlres,
                                   ContainerWriter* container_writer, android::IDiagnostics* diag) {
   pb::internal::CompiledFile pb_compiled_file;
   SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
@@ -538,7 +542,7 @@
     if (context->IsVerbose()) {
       // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
       // This will help catch exotic cases where the new code may generate larger PNGs.
-      std::stringstream legacy_stream(content.to_string());
+      std::stringstream legacy_stream{std::string(content)};
       android::BigBuffer legacy_buffer(4096);
       Png png(context->GetDiagnostics());
       if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 52e113e..612e3a6 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -387,7 +387,7 @@
   }
 
   Context context;
-  const StringPiece& path = args[0];
+  StringPiece path = args[0];
   unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
   if (apk == nullptr) {
     context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 423e939..5bfc732 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -78,7 +78,7 @@
   SymbolTable symbol_table_;
 };
 
-static void EmitDiffLine(const android::Source& source, const StringPiece& message) {
+static void EmitDiffLine(const android::Source& source, StringPiece message) {
   std::cerr << source << ": " << message << "\n";
 }
 
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index a8d2299..97404fc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -126,8 +126,8 @@
     return compilation_package_;
   }
 
-  void SetCompilationPackage(const StringPiece& package_name) {
-    compilation_package_ = package_name.to_string();
+  void SetCompilationPackage(StringPiece package_name) {
+    compilation_package_ = std::string(package_name);
   }
 
   uint8_t GetPackageId() override {
@@ -240,9 +240,9 @@
   IAaptContext* context_;
 };
 
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
-                       const StringPiece& path, bool keep_raw_values, bool utf16,
-                       OutputFormat format, IArchiveWriter* writer) {
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, StringPiece path,
+                       bool keep_raw_values, bool utf16, OutputFormat format,
+                       IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
     context->GetDiagnostics()->Note(android::DiagMessage(path)
@@ -262,8 +262,8 @@
       }
 
       io::BigBufferInputStream input_stream(&buffer);
-      return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
-                                          ArchiveEntry::kCompress, writer);
+      return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress,
+                                          writer);
     } break;
 
     case OutputFormat::kProto: {
@@ -272,8 +272,7 @@
       SerializeXmlOptions options;
       options.remove_empty_text_nodes = (path == kAndroidManifestPath);
       SerializeXmlResourceToPb(xml_res, &pb_node);
-      return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
-                                    writer);
+      return io::CopyProtoToArchive(context, &pb_node, path, ArchiveEntry::kCompress, writer);
     } break;
   }
   return false;
@@ -329,13 +328,13 @@
 };
 
 template <typename T>
-uint32_t GetCompressionFlags(const StringPiece& str, T options) {
+uint32_t GetCompressionFlags(StringPiece str, T options) {
   if (options.do_not_compress_anything) {
     return 0;
   }
 
-  if (options.regex_to_not_compress
-      && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) {
+  if (options.regex_to_not_compress &&
+      std::regex_search(str.begin(), str.end(), options.regex_to_not_compress.value())) {
     return 0;
   }
 
@@ -1176,7 +1175,7 @@
     return bcp47tag;
   }
 
-  std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
+  std::unique_ptr<IArchiveWriter> MakeArchiveWriter(StringPiece out) {
     if (options_.output_to_directory) {
       return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
     } else {
@@ -1212,8 +1211,8 @@
     return false;
   }
 
-  bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
-                     const StringPiece& out_package, const JavaClassGeneratorOptions& java_options,
+  bool WriteJavaFile(ResourceTable* table, StringPiece package_name_to_generate,
+                     StringPiece out_package, const JavaClassGeneratorOptions& java_options,
                      const std::optional<std::string>& out_text_symbols_path = {}) {
     if (!options_.generate_java_class_path && !out_text_symbols_path) {
       return true;
@@ -2473,14 +2472,14 @@
   for (std::string& extra_package : extra_java_packages_) {
     // A given package can actually be a colon separated list of packages.
     for (StringPiece package : util::Split(extra_package, ':')) {
-      options_.extra_java_packages.insert(package.to_string());
+      options_.extra_java_packages.emplace(package);
     }
   }
 
   if (product_list_) {
     for (StringPiece product : util::Tokenize(product_list_.value(), ',')) {
       if (product != "" && product != "default") {
-        options_.products.insert(product.to_string());
+        options_.products.emplace(product);
       }
     }
   }
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 042926c..9c1a2f6 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -370,8 +370,8 @@
 
     if (!kept_artifacts_.empty()) {
       for (const std::string& artifact_str : kept_artifacts_) {
-        for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
-          options_.kept_artifacts.insert(artifact.to_string());
+        for (StringPiece artifact : util::Tokenize(artifact_str, ',')) {
+          options_.kept_artifacts.emplace(artifact);
         }
       }
     }
@@ -403,7 +403,7 @@
 
   if (target_densities_) {
     // Parse the target screen densities.
-    for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) {
+    for (StringPiece config_str : util::Tokenize(target_densities_.value(), ',')) {
       std::optional<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
       if (!target_density) {
         return 1;
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 56e2f52..92849cf 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,8 +34,7 @@
 
 namespace aapt {
 
-std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg,
-                                                    android::IDiagnostics* diag) {
+std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) {
   ConfigDescription preferred_density_config;
   if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
     diag->Error(android::DiagMessage()
@@ -55,7 +54,7 @@
   return preferred_density_config.density;
 }
 
-bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, std::string* out_path,
+bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path,
                          SplitConstraints* out_split) {
   CHECK(diag != nullptr);
   CHECK(out_path != nullptr);
@@ -77,7 +76,7 @@
 
   *out_path = parts[0];
   out_split->name = parts[1];
-  for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
+  for (StringPiece config_str : util::Tokenize(parts[1], ',')) {
     ConfigDescription config;
     if (!ConfigDescription::Parse(config_str, &config)) {
       diag->Error(android::DiagMessage()
@@ -93,7 +92,7 @@
                                                            android::IDiagnostics* diag) {
   std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>();
   for (const std::string& config_arg : args) {
-    for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) {
+    for (StringPiece config_str : util::Tokenize(config_arg, ',')) {
       ConfigDescription config;
       LocaleValue lv;
       if (lv.InitFromFilterString(config_str)) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 3d4ca24..169d5f9 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -34,13 +34,13 @@
 
 // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
 // Returns Nothing and logs a human friendly error message if the string was not legal.
-std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg,
+std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
                                                     android::IDiagnostics* diag);
 
 // Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in
 // `out_path` with the path and `out_split` with the set of ConfigDescriptions.
 // Returns false and logs a human friendly error message if the string was not legal.
-bool ParseSplitParameter(const android::StringPiece& arg, android::IDiagnostics* diag,
+bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag,
                          std::string* out_path, SplitConstraints* out_split);
 
 // Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter.
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index c931da4..4538ecc 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -218,11 +218,9 @@
 
 static bool PopulateBounds(const std::vector<Range>& padding,
                            const std::vector<Range>& layout_bounds,
-                           const std::vector<Range>& stretch_regions,
-                           const int32_t length, int32_t* padding_start,
-                           int32_t* padding_end, int32_t* layout_start,
-                           int32_t* layout_end, const StringPiece& edge_name,
-                           std::string* out_err) {
+                           const std::vector<Range>& stretch_regions, const int32_t length,
+                           int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
+                           int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
   if (padding.size() > 1) {
     std::stringstream err_stream;
     err_stream << "too many padding sections on " << edge_name << " border";
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 7f8d923..a8b7dd1 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -59,7 +59,7 @@
  */
 class PngChunkFilter : public io::InputStream {
  public:
-  explicit PngChunkFilter(const android::StringPiece& data);
+  explicit PngChunkFilter(android::StringPiece data);
   virtual ~PngChunkFilter() = default;
 
   bool Next(const void** buffer, size_t* len) override;
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 4db2392..2e55d0c 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -70,7 +70,7 @@
   }
 }
 
-PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
+PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
   if (util::StartsWith(data_, kPngSignature)) {
     window_start_ = 0;
     window_end_ = kPngSignatureSize;
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index 3a515fa..463ce78 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -20,36 +20,42 @@
 
 using android::StringPiece;
 
+using namespace std::literals;
+
 namespace aapt {
 
 // String basis to generate expansion
-static const std::string kExpansionString =
+static constexpr auto kExpansionString =
     "one two three "
     "four five six seven eight nine ten eleven twelve thirteen "
-    "fourteen fiveteen sixteen seventeen nineteen twenty";
+    "fourteen fiveteen sixteen seventeen nineteen twenty"sv;
 
 // Special unicode characters to override directionality of the words
-static const std::string kRlm = "\u200f";
-static const std::string kRlo = "\u202e";
-static const std::string kPdf = "\u202c";
+static constexpr auto kRlm = "\u200f"sv;
+static constexpr auto kRlo = "\u202e"sv;
+static constexpr auto kPdf = "\u202c"sv;
 
 // Placeholder marks
-static const std::string kPlaceholderOpen = "\u00bb";
-static const std::string kPlaceholderClose = "\u00ab";
+static constexpr auto kPlaceholderOpen = "\u00bb"sv;
+static constexpr auto kPlaceholderClose = "\u00ab"sv;
 
 static const char kArgStart = '{';
 static const char kArgEnd = '}';
 
 class PseudoMethodNone : public PseudoMethodImpl {
  public:
-  std::string Text(const StringPiece& text) override { return text.to_string(); }
-  std::string Placeholder(const StringPiece& text) override { return text.to_string(); }
+  std::string Text(StringPiece text) override {
+    return std::string(text);
+  }
+  std::string Placeholder(StringPiece text) override {
+    return std::string(text);
+  }
 };
 
 class PseudoMethodBidi : public PseudoMethodImpl {
  public:
-  std::string Text(const StringPiece& text) override;
-  std::string Placeholder(const StringPiece& text) override;
+  std::string Text(StringPiece text) override;
+  std::string Placeholder(StringPiece text) override;
 };
 
 class PseudoMethodAccent : public PseudoMethodImpl {
@@ -57,8 +63,8 @@
   PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {}
   std::string Start() override;
   std::string End() override;
-  std::string Text(const StringPiece& text) override;
-  std::string Placeholder(const StringPiece& text) override;
+  std::string Text(StringPiece text) override;
+  std::string Placeholder(StringPiece text) override;
 
  private:
   size_t depth_;
@@ -84,7 +90,7 @@
   }
 }
 
-std::string Pseudolocalizer::Text(const StringPiece& text) {
+std::string Pseudolocalizer::Text(StringPiece text) {
   std::string out;
   size_t depth = last_depth_;
   size_t lastpos, pos;
@@ -116,7 +122,7 @@
       }
       size_t size = nextpos - lastpos;
       if (size) {
-        std::string chunk = text.substr(lastpos, size).to_string();
+        std::string chunk(text.substr(lastpos, size));
         if (pseudo) {
           chunk = impl_->Text(chunk);
         } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
@@ -301,21 +307,23 @@
 }
 
 static std::string PseudoGenerateExpansion(const unsigned int length) {
-  std::string result = kExpansionString;
-  const char* s = result.data();
+  std::string result(kExpansionString);
   if (result.size() < length) {
     result += " ";
     result += PseudoGenerateExpansion(length - result.size());
   } else {
     int ext = 0;
     // Should contain only whole words, so looking for a space
-    for (unsigned int i = length + 1; i < result.size(); ++i) {
-      ++ext;
-      if (s[i] == ' ') {
-        break;
+    {
+      const char* const s = result.data();
+      for (unsigned int i = length + 1; i < result.size(); ++i) {
+        ++ext;
+        if (s[i] == ' ') {
+          break;
+        }
       }
     }
-    result = result.substr(0, length + ext);
+    result.resize(length + ext);
   }
   return result;
 }
@@ -349,7 +357,7 @@
  *
  * Note: This leaves placeholder syntax untouched.
  */
-std::string PseudoMethodAccent::Text(const StringPiece& source) {
+std::string PseudoMethodAccent::Text(StringPiece source) {
   const char* s = source.data();
   std::string result;
   const size_t I = source.size();
@@ -435,12 +443,12 @@
   return result;
 }
 
-std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
+std::string PseudoMethodAccent::Placeholder(StringPiece source) {
   // Surround a placeholder with brackets
-  return kPlaceholderOpen + source.to_string() + kPlaceholderClose;
+  return (std::string(kPlaceholderOpen) += source) += kPlaceholderClose;
 }
 
-std::string PseudoMethodBidi::Text(const StringPiece& source) {
+std::string PseudoMethodBidi::Text(StringPiece source) {
   const char* s = source.data();
   std::string result;
   bool lastspace = true;
@@ -456,10 +464,10 @@
     space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't'));
     if (lastspace && !space) {
       // Word start
-      result += kRlm + kRlo;
+      (result += kRlm) += kRlo;
     } else if (!lastspace && space) {
       // Word end
-      result += kPdf + kRlm;
+      (result += kPdf) += kRlm;
     }
     lastspace = space;
     if (escape) {
@@ -470,14 +478,14 @@
   }
   if (!lastspace) {
     // End of last word
-    result += kPdf + kRlm;
+    (result += kPdf) += kRlm;
   }
   return result;
 }
 
-std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
+std::string PseudoMethodBidi::Placeholder(StringPiece source) {
   // Surround a placeholder with directionality change sequence
-  return kRlm + kRlo + source.to_string() + kPdf + kRlm;
+  return (((std::string(kRlm) += kRlo) += source) += kPdf) += kRlm;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 4dedc70..2b94bcc 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -31,8 +31,8 @@
   virtual ~PseudoMethodImpl() {}
   virtual std::string Start() { return {}; }
   virtual std::string End() { return {}; }
-  virtual std::string Text(const android::StringPiece& text) = 0;
-  virtual std::string Placeholder(const android::StringPiece& text) = 0;
+  virtual std::string Text(android::StringPiece text) = 0;
+  virtual std::string Placeholder(android::StringPiece text) = 0;
 };
 
 class Pseudolocalizer {
@@ -47,7 +47,7 @@
   void SetMethod(Method method);
   std::string Start() { return impl_->Start(); }
   std::string End() { return impl_->End(); }
-  std::string Text(const android::StringPiece& text);
+  std::string Text(android::StringPiece text);
 
  private:
   std::unique_ptr<PseudoMethodImpl> impl_;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 6bba11e..1b03253 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -152,7 +152,7 @@
  * success, or false if the either the placeholder is not found in the name, or the value is not
  * present and the placeholder was.
  */
-bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value,
+bool ReplacePlaceholder(StringPiece placeholder, const std::optional<StringPiece>& value,
                         std::string* name, android::IDiagnostics* diag) {
   size_t offset = name->find(placeholder.data());
   bool found = (offset != std::string::npos);
@@ -338,17 +338,17 @@
   return {config};
 }
 
-const StringPiece& AbiToString(Abi abi) {
+StringPiece AbiToString(Abi abi) {
   return kAbiToStringMap.at(static_cast<size_t>(abi));
 }
 
 /**
  * Returns the common artifact base name from a template string.
  */
-std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name,
+std::optional<std::string> ToBaseName(std::string result, StringPiece apk_name,
                                       android::IDiagnostics* diag) {
   const StringPiece ext = file::GetExtension(apk_name);
-  size_t end_index = apk_name.to_string().rfind(ext.to_string());
+  size_t end_index = apk_name.rfind(ext);
   const std::string base_name =
       (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : "";
 
@@ -371,17 +371,17 @@
     // If no extension is specified, and the name template does not end in the current extension,
     // add the existing extension.
     if (!util::EndsWith(result, ext)) {
-      result.append(ext.to_string());
+      result.append(ext);
     }
   }
 
   return result;
 }
 
-std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
-                                                              const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::ToArtifactName(StringPiece format,
+                                                              StringPiece apk_name,
                                                               android::IDiagnostics* diag) const {
-  std::optional<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
+  std::optional<std::string> base = ToBaseName(std::string(format), apk_name, diag);
   if (!base) {
     return {};
   }
@@ -414,7 +414,7 @@
   return result;
 }
 
-std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::Name(StringPiece apk_name,
                                                     android::IDiagnostics* diag) const {
   if (!name) {
     return {};
@@ -439,7 +439,7 @@
 }
 
 std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse(
-    const android::StringPiece& apk_path) {
+    android::StringPiece apk_path) {
   std::optional<PostProcessingConfiguration> maybe_config =
       ExtractConfiguration(contents_, config_path_, diag_);
   if (!maybe_config) {
@@ -447,7 +447,7 @@
   }
 
   // Convert from a parsed configuration to a list of artifacts for processing.
-  const std::string& apk_name = file::GetFilename(apk_path).to_string();
+  const std::string apk_name(file::GetFilename(apk_path));
   std::vector<OutputArtifact> output_artifacts;
 
   PostProcessingConfiguration& config = maybe_config.value();
@@ -519,7 +519,7 @@
   for (auto& node : root_element->children) {
     xml::Text* t;
     if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-      config->artifact_format = TrimWhitespace(t->text).to_string();
+      config->artifact_format.emplace(TrimWhitespace(t->text));
       break;
     }
   }
@@ -561,7 +561,7 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text));
           if (abi != kStringToAbiMap.end()) {
             group.push_back(abi->second);
           } else {
@@ -622,7 +622,7 @@
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
-          const android::StringPiece& text = TrimWhitespace(t->text);
+          android::StringPiece text = TrimWhitespace(t->text);
           bool parsed = ConfigDescription::Parse(text, &config_descriptor);
           if (parsed &&
               (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -688,7 +688,7 @@
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
-          const android::StringPiece& text = TrimWhitespace(t->text);
+          android::StringPiece text = TrimWhitespace(t->text);
           bool parsed = ConfigDescription::Parse(text, &config_descriptor);
           if (parsed &&
               (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -806,7 +806,7 @@
         for (auto& node : element->children) {
           xml::Text* t;
           if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-            result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
+            result.texture_paths.emplace_back(TrimWhitespace(t->text));
           }
         }
       }
@@ -843,7 +843,7 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          group.push_back(TrimWhitespace(t->text).to_string());
+          group.emplace_back(TrimWhitespace(t->text));
           break;
         }
       }
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 2c8221d..d66f4ab 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -43,7 +43,7 @@
 };
 
 /** Helper method to convert an ABI to a string representing the path within the APK. */
-const android::StringPiece& AbiToString(Abi abi);
+android::StringPiece AbiToString(Abi abi);
 
 /**
  * Represents an individual locale. When a locale is included, it must be
@@ -150,8 +150,7 @@
    * Parses the configuration file and returns the results. If the configuration could not be parsed
    * the result is empty and any errors will be displayed with the provided diagnostics context.
    */
-  std::optional<std::vector<configuration::OutputArtifact>> Parse(
-      const android::StringPiece& apk_path);
+  std::optional<std::vector<configuration::OutputArtifact>> Parse(android::StringPiece apk_path);
 
  protected:
   /**
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 3028c3f..198f730 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -138,13 +138,12 @@
   std::optional<std::string> gl_texture_group;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  std::optional<std::string> ToArtifactName(const android::StringPiece& format,
-                                            const android::StringPiece& apk_name,
+  std::optional<std::string> ToArtifactName(android::StringPiece format,
+                                            android::StringPiece apk_name,
                                             android::IDiagnostics* diag) const;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  std::optional<std::string> Name(const android::StringPiece& apk_name,
-                                  android::IDiagnostics* diag) const;
+  std::optional<std::string> Name(android::StringPiece apk_name, android::IDiagnostics* diag) const;
 };
 
 /** AAPT2 XML configuration file binary representation. */
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c4c002d..d60869a 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1076,7 +1076,7 @@
 
   /** Adds a feature to the feature group. */
   void AddFeature(const std::string& name, bool required = true, int32_t version = -1) {
-    features_.insert(std::make_pair(name, Feature{ required, version }));
+    features_.insert_or_assign(name, Feature{required, version});
     if (required) {
       if (name == "android.hardware.camera.autofocus" ||
           name == "android.hardware.camera.flash") {
@@ -1348,6 +1348,11 @@
   std::string impliedReason;
 
   void Extract(xml::Element* element) override {
+    const auto parent_stack = extractor()->parent_stack();
+    if (!extractor()->options_.only_permissions &&
+        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+      return;
+    }
     name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
     std::string feature =
         GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
@@ -1472,6 +1477,11 @@
   const int32_t* maxSdkVersion = nullptr;
 
   void Extract(xml::Element* element) override {
+    const auto parent_stack = extractor()->parent_stack();
+    if (!extractor()->options_.only_permissions &&
+        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+      return;
+    }
     name = GetAttributeString(FindAttribute(element, NAME_ATTR));
     maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR));
 
diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp
index 9ace82a..908b171 100644
--- a/tools/aapt2/filter/AbiFilter.cpp
+++ b/tools/aapt2/filter/AbiFilter.cpp
@@ -23,15 +23,15 @@
 namespace aapt {
 
 std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) {
-  std::unordered_set<std::string> abi_set;
+  std::unordered_set<std::string_view> abi_set;
   for (auto& abi : abi_list) {
-    abi_set.insert(configuration::AbiToString(abi).to_string());
+    abi_set.insert(configuration::AbiToString(abi));
   }
   // Make unique by hand as the constructor is private.
-  return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set));
+  return std::unique_ptr<AbiFilter>(new AbiFilter(std::move(abi_set)));
 }
 
-bool AbiFilter::Keep(const std::string& path) {
+bool AbiFilter::Keep(std::string_view path) {
   // We only care about libraries.
   if (!util::StartsWith(path, kLibPrefix)) {
     return true;
@@ -44,7 +44,7 @@
   }
 
   // Strip the lib/ prefix.
-  const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
+  const auto path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
   return (abis_.find(path_abi) != abis_.end());
 }
 
diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h
index 2832711..7380f3f 100644
--- a/tools/aapt2/filter/AbiFilter.h
+++ b/tools/aapt2/filter/AbiFilter.h
@@ -18,7 +18,7 @@
 #define AAPT2_ABISPLITTER_H
 
 #include <memory>
-#include <string>
+#include <string_view>
 #include <unordered_set>
 #include <vector>
 
@@ -39,16 +39,16 @@
   static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list);
 
   /** Returns true if the path is for a native library in the list of desired ABIs. */
-  bool Keep(const std::string& path) override;
+  bool Keep(std::string_view path) override;
 
  private:
-  explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) {
+  explicit AbiFilter(std::unordered_set<std::string_view> abis) : abis_(std::move(abis)) {
   }
 
   /** The path prefix to where all native libs end up inside an APK file. */
   static constexpr const char* kLibPrefix = "lib/";
   static constexpr size_t kLibPrefixLen = 4;
-  const std::unordered_set<std::string> abis_;
+  const std::unordered_set<std::string_view> abis_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h
index f932f9c..baf4791 100644
--- a/tools/aapt2/filter/Filter.h
+++ b/tools/aapt2/filter/Filter.h
@@ -18,6 +18,7 @@
 #define AAPT2_FILTER_H
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "util/Util.h"
@@ -30,7 +31,7 @@
   virtual ~IPathFilter() = default;
 
   /** Returns true if the path should be kept. */
-  virtual bool Keep(const std::string& path) = 0;
+  virtual bool Keep(std::string_view path) = 0;
 };
 
 /**
@@ -42,7 +43,7 @@
   }
 
   /** Returns true if the provided path matches the prefix. */
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     return util::StartsWith(path, prefix_);
   }
 
@@ -59,7 +60,7 @@
   }
 
   /** Returns true if all filters keep the path. */
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     for (auto& filter : filters_) {
       if (!filter->Keep(path)) {
         return false;
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index 80c1618..e9a93d8 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -40,8 +40,8 @@
  public:
   DirectoryWriter() = default;
 
-  bool Open(const StringPiece& out_dir) {
-    dir_ = out_dir.to_string();
+  bool Open(StringPiece out_dir) {
+    dir_ = std::string(out_dir);
     file::FileType type = file::GetFileType(dir_);
     if (type == file::FileType::kNonExistant) {
       error_ = "directory does not exist";
@@ -53,14 +53,14 @@
     return true;
   }
 
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+  bool StartEntry(StringPiece path, uint32_t flags) override {
     if (file_) {
       return false;
     }
 
     std::string full_path = dir_;
     file::AppendPath(&full_path, path);
-    file::mkdirs(file::GetStem(full_path).to_string());
+    file::mkdirs(std::string(file::GetStem(full_path)));
 
     file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};
     if (!file_) {
@@ -91,7 +91,7 @@
     return true;
   }
 
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     if (!StartEntry(path, flags)) {
       return false;
     }
@@ -132,8 +132,8 @@
  public:
   ZipFileWriter() = default;
 
-  bool Open(const StringPiece& path) {
-    file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};
+  bool Open(StringPiece path) {
+    file_ = {::android::base::utf8::fopen(path.data(), "w+b"), fclose};
     if (!file_) {
       error_ = SystemErrorCodeToString(errno);
       return false;
@@ -142,7 +142,7 @@
     return true;
   }
 
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+  bool StartEntry(StringPiece path, uint32_t flags) override {
     if (!writer_) {
       return false;
     }
@@ -182,7 +182,7 @@
     return true;
   }
 
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     while (true) {
       if (!StartEntry(path, flags)) {
         return false;
@@ -257,7 +257,7 @@
 }  // namespace
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
-                                                             const StringPiece& path) {
+                                                             StringPiece path) {
   std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
   if (!writer->Open(path)) {
     diag->Error(android::DiagMessage(path) << writer->GetError());
@@ -267,7 +267,7 @@
 }
 
 std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
-                                                           const StringPiece& path) {
+                                                           StringPiece path) {
   std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
   if (!writer->Open(path)) {
     diag->Error(android::DiagMessage(path) << writer->GetError());
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
index 55b0b2f..6cde753 100644
--- a/tools/aapt2/format/Archive.h
+++ b/tools/aapt2/format/Archive.h
@@ -46,12 +46,12 @@
  public:
   virtual ~IArchiveWriter() = default;
 
-  virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+  virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0;
 
   // Starts a new entry and allows caller to write bytes to it sequentially.
   // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
   // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
-  virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
+  virtual bool StartEntry(android::StringPiece path, uint32_t flags) = 0;
 
   // Called to finish writing an entry previously started by StartEntry.
   // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
@@ -70,10 +70,10 @@
 };
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
-                                                             const android::StringPiece& path);
+                                                             android::StringPiece path);
 
 std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
-                                                           const android::StringPiece& path);
+                                                           android::StringPiece path);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index ceed374..3c44da7 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -50,7 +50,7 @@
 }
 
 std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) {
-  file::mkdirs(file::GetStem(output_path).to_string());
+  file::mkdirs(std::string(file::GetStem(output_path)));
   std::remove(output_path.c_str());
 
   StdErrDiagnostics diag;
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 8291862..75dcba5 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -373,7 +373,7 @@
   std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type_str);
   if (!parsed_type) {
     diag_->Warn(android::DiagMessage(source_)
-                << "invalid type name '" << type_str << "' for type with ID " << type->id);
+                << "invalid type name '" << type_str << "' for type with ID " << int(type->id));
     return true;
   }
 
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index d08b4a3..0f11685 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -84,7 +84,7 @@
     return ::testing::AssertionSuccess();
   }
 
-  ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name,
+  ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name,
                                     const ResourceId& expected_id,
                                     const ConfigDescription& expected_config,
                                     const uint8_t expected_data_type, const uint32_t expected_data,
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 983e646..05f9751 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -79,7 +79,7 @@
   }
 
   void Visit(const xml::Text* node) override {
-    std::string text = util::TrimWhitespace(node->text).to_string();
+    std::string text(util::TrimWhitespace(node->text));
 
     // Skip whitespace only text nodes.
     if (text.empty()) {
@@ -88,10 +88,10 @@
 
     // Compact leading and trailing whitespace into a single space
     if (isspace(node->text[0])) {
-      text = ' ' + text;
+      text.insert(text.begin(), ' ');
     }
-    if (isspace(node->text[node->text.length() - 1])) {
-      text = text + ' ';
+    if (isspace(node->text.back())) {
+      text += ' ';
     }
 
     ChunkWriter writer(buffer_);
@@ -165,7 +165,7 @@
   // We are adding strings to a StringPool whose strings will be sorted and merged with other
   // string pools. That means we can't encode the ID of a string directly. Instead, we defer the
   // writing of the ID here, until after the StringPool is merged and sorted.
-  void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
+  void AddString(StringPiece str, uint32_t priority, android::ResStringPool_ref* dest,
                  bool treat_empty_string_as_null = false) {
     if (str.empty() && treat_empty_string_as_null) {
       // Some parts of the runtime treat null differently than empty string.
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 5adc5e6..ecfdba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -35,7 +35,7 @@
 
 class MockFileCollection : public io::IFileCollection {
  public:
-  MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+  MOCK_METHOD1(FindFile, io::IFile*(StringPiece path));
   MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
   MOCK_METHOD0(GetDirSeparator, char());
 };
@@ -491,7 +491,7 @@
   EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data));
 }
 
-static void ExpectConfigSerializes(const StringPiece& config_str) {
+static void ExpectConfigSerializes(StringPiece config_str) {
   const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
   pb::Configuration pb_config;
   SerializeConfig(expected_config, &pb_config);
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 422658a..08d497d 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -101,7 +101,7 @@
  public:
   virtual ~IFileCollection() = default;
 
-  virtual IFile* FindFile(const android::StringPiece& path) = 0;
+  virtual IFile* FindFile(android::StringPiece path) = 0;
   virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0;
   virtual char GetDirSeparator() = 0;
 };
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 3f071af..a64982a 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -67,8 +67,8 @@
   return result;
 }
 
-std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root,
-                                                        std::string* outError) {
+std::unique_ptr<FileCollection> FileCollection::Create(android::StringPiece root,
+                                                       std::string* outError) {
   std::unique_ptr<FileCollection> collection =
       std::unique_ptr<FileCollection>(new FileCollection());
 
@@ -80,7 +80,7 @@
 
   std::vector<std::string> sorted_files;
   while (struct dirent *entry = readdir(d.get())) {
-    std::string prefix_path = root.to_string();
+    std::string prefix_path(root);
     file::AppendPath(&prefix_path, entry->d_name);
 
     // The directory to iterate over looking for files
@@ -117,12 +117,19 @@
   return collection;
 }
 
-IFile* FileCollection::InsertFile(const StringPiece& path) {
-  return (files_[path.to_string()] = util::make_unique<RegularFile>(android::Source(path))).get();
+IFile* FileCollection::InsertFile(StringPiece path) {
+  auto file = util::make_unique<RegularFile>(android::Source(path));
+  auto it = files_.lower_bound(path);
+  if (it != files_.end() && it->first == path) {
+    it->second = std::move(file);
+  } else {
+    it = files_.emplace_hint(it, path, std::move(file));
+  }
+  return it->second.get();
 }
 
-IFile* FileCollection::FindFile(const StringPiece& path) {
-  auto iter = files_.find(path.to_string());
+IFile* FileCollection::FindFile(StringPiece path) {
+  auto iter = files_.find(path);
   if (iter != files_.end()) {
     return iter->second.get();
   }
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index bc03b9b..0e798fc 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -60,12 +60,11 @@
   FileCollection() = default;
 
   /** Creates a file collection containing all files contained in the specified root directory. */
-  static std::unique_ptr<FileCollection> Create(const android::StringPiece& path,
-                                                std::string* outError);
+  static std::unique_ptr<FileCollection> Create(android::StringPiece path, std::string* outError);
 
   // Adds a file located at path. Returns the IFile representation of that file.
-  IFile* InsertFile(const android::StringPiece& path);
-  IFile* FindFile(const android::StringPiece& path) override;
+  IFile* InsertFile(android::StringPiece path);
+  IFile* FindFile(android::StringPiece path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
   char GetDirSeparator() override;
 
@@ -74,7 +73,7 @@
 
   friend class FileCollectionIterator;
 
-  std::map<std::string, std::unique_ptr<IFile>> files_;
+  std::map<std::string, std::unique_ptr<IFile>, std::less<>> files_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
index 4ca04a8..9c49788 100644
--- a/tools/aapt2/io/StringStream.cpp
+++ b/tools/aapt2/io/StringStream.cpp
@@ -21,7 +21,7 @@
 namespace aapt {
 namespace io {
 
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+StringInputStream::StringInputStream(StringPiece str) : str_(str), offset_(0u) {
 }
 
 bool StringInputStream::Next(const void** data, size_t* size) {
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
index f29890a..f7bdecca 100644
--- a/tools/aapt2/io/StringStream.h
+++ b/tools/aapt2/io/StringStream.h
@@ -29,7 +29,7 @@
 
 class StringInputStream : public KnownSizeInputStream {
  public:
-  explicit StringInputStream(const android::StringPiece& str);
+  explicit StringInputStream(android::StringPiece str);
 
   bool Next(const void** data, size_t* size) override;
 
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index afe54d4..79d8d52 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -26,7 +26,7 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
                               uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
@@ -43,7 +43,7 @@
   return true;
 }
 
-bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, io::IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   std::unique_ptr<io::IData> data = file->OpenAsData();
@@ -56,13 +56,13 @@
 }
 
 bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file,
-                                          const std::string& out_path, IArchiveWriter* writer) {
+                                          std::string_view out_path, IArchiveWriter* writer) {
   uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
   return CopyFileToArchive(context, file, out_path, compression_flags, writer);
 }
 
 bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
-                        const std::string& out_path, uint32_t compression_flags,
+                        std::string_view out_path, uint32_t compression_flags,
                         IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
@@ -110,7 +110,7 @@
   return !in->HadError();
 }
 
-bool Copy(OutputStream* out, const StringPiece& in) {
+bool Copy(OutputStream* out, StringPiece in) {
   const char* in_buffer = in.data();
   size_t in_len = in.size();
   while (in_len != 0) {
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 1b48a28..685f522 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -17,12 +17,11 @@
 #ifndef AAPT_IO_UTIL_H
 #define AAPT_IO_UTIL_H
 
-#include <string>
-
-#include "google/protobuf/message.h"
-#include "google/protobuf/io/coded_stream.h"
+#include <string_view>
 
 #include "format/Archive.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/message.h"
 #include "io/File.h"
 #include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
@@ -30,23 +29,23 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
                               uint32_t compression_flags, IArchiveWriter* writer);
 
-bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer);
 
 bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
-                                          const std::string& out_path, IArchiveWriter* writer);
+                                          std::string_view out_path, IArchiveWriter* writer);
 
 bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
-                        const std::string& out_path, uint32_t compression_flags,
+                        std::string_view out_path, uint32_t compression_flags,
                         IArchiveWriter* writer);
 
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
-bool Copy(OutputStream* out, const ::android::StringPiece& in);
+bool Copy(OutputStream* out, android::StringPiece in);
 bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
 
 class OutputStreamAdaptor : public io::OutputStream {
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 400269c..4a5385d 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -91,8 +91,8 @@
 
 ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
 
-std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
-    const StringPiece& path, std::string* out_error) {
+std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(StringPiece path,
+                                                             std::string* out_error) {
   TRACE_CALL();
   constexpr static const int32_t kEmptyArchive = -6;
 
@@ -130,8 +130,8 @@
       continue;
     }
 
-    std::unique_ptr<IFile> file = util::make_unique<ZipFile>(
-        collection->handle_, zip_data, android::Source(zip_entry_path, path.to_string()));
+    std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data,
+                                                             android::Source(zip_entry_path, path));
     collection->files_by_name_[zip_entry_path] = file.get();
     collection->files_.push_back(std::move(file));
   }
@@ -144,8 +144,8 @@
   return collection;
 }
 
-IFile* ZipFileCollection::FindFile(const StringPiece& path) {
-  auto iter = files_by_name_.find(path.to_string());
+IFile* ZipFileCollection::FindFile(StringPiece path) {
+  auto iter = files_by_name_.find(path);
   if (iter != files_by_name_.end()) {
     return iter->second;
   }
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 78c9c21..c263aa4 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -61,10 +61,10 @@
 // An IFileCollection that represents a ZIP archive and the entries within it.
 class ZipFileCollection : public IFileCollection {
  public:
-  static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
+  static std::unique_ptr<ZipFileCollection> Create(android::StringPiece path,
                                                    std::string* outError);
 
-  io::IFile* FindFile(const android::StringPiece& path) override;
+  io::IFile* FindFile(android::StringPiece path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
   char GetDirSeparator() override;
 
@@ -76,7 +76,7 @@
 
   ZipArchiveHandle handle_;
   std::vector<std::unique_ptr<IFile>> files_;
-  std::map<std::string, IFile*> files_by_name_;
+  std::map<std::string, IFile*, std::less<>> files_by_name_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 482d91a..87da09a 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -30,7 +30,7 @@
 
 namespace aapt {
 
-StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) {
+StringPiece AnnotationProcessor::ExtractFirstSentence(StringPiece comment) {
   Utf8Iterator iter(comment);
   while (iter.HasNext()) {
     const char32_t codepoint = iter.Next();
@@ -62,7 +62,7 @@
 }};
 
 void AnnotationProcessor::AppendCommentLine(std::string comment) {
-  static const std::string sDeprecated = "@deprecated";
+  static constexpr std::string_view sDeprecated = "@deprecated";
 
   // Treat deprecated specially, since we don't remove it from the source comment.
   if (comment.find(sDeprecated) != std::string::npos) {
@@ -74,7 +74,7 @@
     if (idx != std::string::npos) {
       // Captures all parameters associated with the specified annotation rule
       // by matching the first pair of parantheses after the rule.
-      std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)");
+      std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)");
       std::smatch match_result;
       const bool is_match = std::regex_search(comment, match_result, re);
       // We currently only capture and preserve parameters for SystemApi.
@@ -97,7 +97,7 @@
 
   // If there was trimming to do, copy the string.
   if (trimmed.size() != comment.size()) {
-    comment = trimmed.to_string();
+    comment = std::string(trimmed);
   }
 
   if (!has_comments_) {
@@ -107,12 +107,12 @@
   comment_ << "\n * " << std::move(comment);
 }
 
-void AnnotationProcessor::AppendComment(const StringPiece& comment) {
+void AnnotationProcessor::AppendComment(StringPiece comment) {
   // We need to process line by line to clean-up whitespace and append prefixes.
   for (StringPiece line : util::Tokenize(comment, '\n')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      AppendCommentLine(line.to_string());
+      AppendCommentLine(std::string(line));
     }
   }
 }
@@ -126,7 +126,7 @@
 void AnnotationProcessor::Print(Printer* printer, bool strip_api_annotations) const {
   if (has_comments_) {
     std::string result = comment_.str();
-    for (const StringPiece& line : util::Tokenize(result, '\n')) {
+    for (StringPiece line : util::Tokenize(result, '\n')) {
       printer->Println(line);
     }
     printer->Println(" */");
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index f217afb..db3437e 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -56,11 +56,11 @@
   // Extracts the first sentence of a comment. The algorithm selects the substring starting from
   // the beginning of the string, and ending at the first '.' character that is followed by a
   // whitespace character. If these requirements are not met, the whole string is returned.
-  static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment);
+  static android::StringPiece ExtractFirstSentence(android::StringPiece comment);
 
   // Adds more comments. Resources can have value definitions for various configurations, and
   // each of the definitions may have comments that need to be processed.
-  void AppendComment(const android::StringPiece& comment);
+  void AppendComment(android::StringPiece comment);
 
   void AppendNewLine();
 
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 3163497..98f3bd2 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -27,8 +27,8 @@
   processor_.Print(printer, strip_api_annotations);
 }
 
-void MethodDefinition::AppendStatement(const StringPiece& statement) {
-  statements_.push_back(statement.to_string());
+void MethodDefinition::AppendStatement(StringPiece statement) {
+  statements_.emplace_back(statement);
 }
 
 void MethodDefinition::Print(bool final, Printer* printer, bool) const {
@@ -110,8 +110,8 @@
     " * should not be modified by hand.\n"
     " */\n\n";
 
-void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package,
-                                    bool final, bool strip_api_annotations, io::OutputStream* out) {
+void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final,
+                                    bool strip_api_annotations, io::OutputStream* out) {
   Printer printer(out);
   printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
   printer.Println();
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 2acdadb..63c9982 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -59,8 +59,8 @@
 template <typename T>
 class PrimitiveMember : public ClassMember {
  public:
-  PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false)
-      : name_(name.to_string()), val_(val), staged_api_(staged_api) {
+  PrimitiveMember(android::StringPiece name, const T& val, bool staged_api = false)
+      : name_(name), val_(val), staged_api_(staged_api) {
   }
 
   bool empty() const override {
@@ -104,8 +104,8 @@
 template <>
 class PrimitiveMember<std::string> : public ClassMember {
  public:
-  PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false)
-      : name_(name.to_string()), val_(val) {
+  PrimitiveMember(android::StringPiece name, const std::string& val, bool staged_api = false)
+      : name_(name), val_(val) {
   }
 
   bool empty() const override {
@@ -141,7 +141,8 @@
 template <typename T, typename StringConverter>
 class PrimitiveArrayMember : public ClassMember {
  public:
-  explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
+  explicit PrimitiveArrayMember(android::StringPiece name) : name_(name) {
+  }
 
   void AddElement(const T& val) {
     elements_.emplace_back(val);
@@ -209,12 +210,12 @@
 class MethodDefinition : public ClassMember {
  public:
   // Expected method signature example: 'public static void onResourcesLoaded(int p)'.
-  explicit MethodDefinition(const android::StringPiece& signature)
-      : signature_(signature.to_string()) {}
+  explicit MethodDefinition(android::StringPiece signature) : signature_(signature) {
+  }
 
   // Appends a single statement to the method. It should include no newlines or else
   // formatting may be broken.
-  void AppendStatement(const android::StringPiece& statement);
+  void AppendStatement(android::StringPiece statement);
 
   // Not quite the same as a name, but good enough.
   const std::string& GetName() const override {
@@ -239,11 +240,12 @@
 
 class ClassDefinition : public ClassMember {
  public:
-  static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
-                            bool final, bool strip_api_annotations, io::OutputStream* out);
+  static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final,
+                            bool strip_api_annotations, io::OutputStream* out);
 
-  ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
-      : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
+  ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty)
+      : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) {
+  }
 
   enum class Result {
     kAdded,
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index a25ca22..7665d0e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -57,14 +57,14 @@
     "transient",  "try",          "void",      "volatile",   "while",
     "true",       "false",        "null"};
 
-static bool IsValidSymbol(const StringPiece& symbol) {
+static bool IsValidSymbol(StringPiece symbol) {
   return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
 }
 
 // Java symbols can not contain . or -, but those are valid in a resource name.
 // Replace those with '_'.
-std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
-  std::string output = symbol.to_string();
+std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) {
+  std::string output(symbol);
   for (char& c : output) {
     if (c == '.' || c == '-') {
       c = '_';
@@ -84,7 +84,7 @@
 // Foo_bar
 static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
                                        const std::string& styleable_class_name,
-                                       const StringPiece& package_name_to_generate) {
+                                       StringPiece package_name_to_generate) {
   std::string output = styleable_class_name;
 
   // We may reference IDs from other packages, so prefix the entry name with
@@ -226,16 +226,15 @@
 
 static FieldReference GetRFieldReference(const ResourceName& name,
                                          StringPiece fallback_package_name) {
-  const std::string package_name =
-      name.package.empty() ? fallback_package_name.to_string() : name.package;
+  const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package;
   const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
-  return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(),
-                                     name.type.to_string().data(), entry.c_str()));
+  return FieldReference(
+      StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str()));
 }
 
 bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
                                           const Styleable& styleable,
-                                          const StringPiece& package_name_to_generate,
+                                          StringPiece package_name_to_generate,
                                           ClassDefinition* out_class_def,
                                           MethodDefinition* out_rewrite_method,
                                           Printer* r_txt_printer) {
@@ -314,7 +313,8 @@
         return true;
       }
       const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
-      return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
+      return attr_comment_line.find("@removed") != std::string::npos ||
+             attr_comment_line.find("@hide") != std::string::npos;
     });
     documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
 
@@ -397,7 +397,7 @@
         comment = styleable_attr.symbol.value().attribute->GetComment();
       }
 
-      if (comment.contains("@removed")) {
+      if (comment.find("@removed") != std::string::npos) {
         // Removed attributes are public but hidden from the documentation, so
         // don't emit them as part of the class documentation.
         continue;
@@ -497,7 +497,7 @@
   }
 
   if (out_rewrite_method != nullptr) {
-    const std::string type_str = name.type.to_string();
+    const auto type_str = name.type.to_string();
     out_rewrite_method->AppendStatement(
         StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
                      field_name.data(), type_str.data(), field_name.data()));
@@ -505,8 +505,7 @@
 }
 
 std::optional<std::string> JavaClassGenerator::UnmangleResource(
-    const StringPiece& package_name, const StringPiece& package_name_to_generate,
-    const ResourceEntry& entry) {
+    StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) {
   if (SkipSymbol(entry.visibility.level)) {
     return {};
   }
@@ -528,7 +527,7 @@
   return {std::move(unmangled_name)};
 }
 
-bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
+bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate,
                                      const ResourceTablePackage& package,
                                      const ResourceTableType& type,
                                      ClassDefinition* out_type_class_def,
@@ -577,7 +576,7 @@
   return true;
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out,
                                   OutputStream* out_r_txt) {
   return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
 }
@@ -591,8 +590,8 @@
   }
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
-                                  const StringPiece& out_package_name, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate,
+                                  StringPiece out_package_name, OutputStream* out,
                                   OutputStream* out_r_txt) {
   ClassDefinition r_class("R", ClassQualifier::kNone, true);
   std::unique_ptr<MethodDefinition> rewrite_method;
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index b45a2f1..234df04 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -70,16 +70,16 @@
   // All symbols technically belong to a single package, but linked libraries will
   // have their names mangled, denoting that they came from a different package.
   // We need to generate these symbols in a separate file. Returns true on success.
-  bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out,
+  bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out,
                 io::OutputStream* out_r_txt = nullptr);
 
-  bool Generate(const android::StringPiece& package_name_to_generate,
-                const android::StringPiece& output_package_name, io::OutputStream* out,
+  bool Generate(android::StringPiece package_name_to_generate,
+                android::StringPiece output_package_name, io::OutputStream* out,
                 io::OutputStream* out_r_txt = nullptr);
 
   const std::string& GetError() const;
 
-  static std::string TransformToFieldName(const android::StringPiece& symbol);
+  static std::string TransformToFieldName(android::StringPiece symbol);
 
  private:
   bool SkipSymbol(Visibility::Level state);
@@ -87,11 +87,11 @@
 
   // Returns the unmangled resource entry name if the unmangled package is the same as
   // package_name_to_generate. Returns nothing if the resource should be skipped.
-  std::optional<std::string> UnmangleResource(const android::StringPiece& package_name,
-                                              const android::StringPiece& package_name_to_generate,
+  std::optional<std::string> UnmangleResource(android::StringPiece package_name,
+                                              android::StringPiece package_name_to_generate,
                                               const ResourceEntry& entry);
 
-  bool ProcessType(const android::StringPiece& package_name_to_generate,
+  bool ProcessType(android::StringPiece package_name_to_generate,
                    const ResourceTablePackage& package, const ResourceTableType& type,
                    ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def,
                    text::Printer* r_txt_printer);
@@ -106,8 +106,7 @@
   // its package ID if `out_rewrite_method` is not nullptr.
   // `package_name_to_generate` is the package
   bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
-                        const Styleable& styleable,
-                        const android::StringPiece& package_name_to_generate,
+                        const Styleable& styleable, android::StringPiece package_name_to_generate,
                         ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method,
                         text::Printer* r_txt_printer);
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index d0850b8..56d9075 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -646,8 +646,8 @@
   return true;
 }
 
-static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns,
-                                  const StringPiece& attr_name, xml::Element* el) {
+static void FullyQualifyClassName(StringPiece package, StringPiece attr_ns, StringPiece attr_name,
+                                  xml::Element* el) {
   xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name);
   if (attr != nullptr) {
     if (std::optional<std::string> new_value =
@@ -657,7 +657,7 @@
   }
 }
 
-static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
+static bool RenameManifestPackage(StringPiece package_override, xml::Element* manifest_el) {
   xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
 
   // We've already verified that the manifest element is present, with a package
@@ -665,7 +665,7 @@
   CHECK(attr != nullptr);
 
   std::string original_package = std::move(attr->value);
-  attr->value = package_override.to_string();
+  attr->value.assign(package_override);
 
   xml::Element* application_el = manifest_el->FindChild({}, "application");
   if (application_el != nullptr) {
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 8d1a647..7180ae6 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -61,12 +61,12 @@
             .Build();
   }
 
-  std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) {
+  std::unique_ptr<xml::XmlResource> Verify(StringPiece str) {
     return VerifyWithOptions(str, {});
   }
 
-  std::unique_ptr<xml::XmlResource> VerifyWithOptions(
-      const StringPiece& str, const ManifestFixerOptions& options) {
+  std::unique_ptr<xml::XmlResource> VerifyWithOptions(StringPiece str,
+                                                      const ManifestFixerOptions& options) {
     std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str);
     ManifestFixer fixer(options);
     if (fixer.Consume(mContext.get(), doc.get())) {
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index f2a93a8..9dadfb2 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -189,8 +189,7 @@
  public:
   EmptyDeclStack() = default;
 
-  std::optional<xml::ExtractedPackage> TransformPackageAlias(
-      const StringPiece& alias) const override {
+  std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
     if (alias.empty()) {
       return xml::ExtractedPackage{{}, true /*private*/};
     }
@@ -206,8 +205,7 @@
       : alias_namespaces_(std::move(namespaces)) {
   }
 
-  std::optional<xml::ExtractedPackage> TransformPackageAlias(
-      const StringPiece& alias) const override {
+  std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
     if (alias.empty()) {
       return xml::ExtractedPackage{{}, true /*private*/};
     }
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c9f0964..67a4828 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -66,7 +66,7 @@
 
 // This will merge and mangle resources from a static library. It is assumed that all FileReferences
 // have correctly set their io::IFile*.
-bool TableMerger::MergeAndMangle(const android::Source& src, const StringPiece& package_name,
+bool TableMerger::MergeAndMangle(const android::Source& src, StringPiece package_name,
                                  ResourceTable* table) {
   bool error = false;
   for (auto& package : table->packages) {
@@ -326,8 +326,8 @@
     const std::string& package, const FileReference& file_ref) {
   StringPiece prefix, entry, suffix;
   if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
-    std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
-    std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
+    std::string mangled_entry = NameMangler::MangleEntry(package, entry);
+    std::string newPath = (std::string(prefix) += mangled_entry) += suffix;
     std::unique_ptr<FileReference> new_file_ref =
         util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath));
     new_file_ref->SetComment(file_ref.GetComment());
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 2ba2123..37daf42 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -61,7 +61,7 @@
   // References are made to this ResourceTable for efficiency reasons.
   TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options);
 
-  inline const std::set<std::string>& merged_packages() const {
+  inline const std::set<std::string, std::less<>>& merged_packages() const {
     return merged_packages_;
   }
 
@@ -71,7 +71,7 @@
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
   // All FileReference values must have their io::IFile set.
-  bool MergeAndMangle(const android::Source& src, const android::StringPiece& package,
+  bool MergeAndMangle(const android::Source& src, android::StringPiece package,
                       ResourceTable* table);
 
   // Merges a compiled file that belongs to this same or empty package.
@@ -84,7 +84,7 @@
   ResourceTable* main_table_;
   TableMergerOptions options_;
   ResourceTablePackage* main_package_;
-  std::set<std::string> merged_packages_;
+  std::set<std::string, std::less<>> merged_packages_;
 
   bool MergeImpl(const android::Source& src, ResourceTable* src_table, bool overlay,
                  bool allow_new);
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index f994e27..f01db3d 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -113,12 +113,12 @@
 };
 
 class SignatureFilter : public IPathFilter {
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
-    if (std::regex_search(path, signature_regex)) {
+    if (std::regex_search(path.begin(), path.end(), signature_regex)) {
       return false;
     }
-    return !(path == "META-INF/MANIFEST.MF");
+    return path != "META-INF/MANIFEST.MF";
   }
 };
 
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index f704f26..1fdd728 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -35,7 +35,7 @@
 Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
 }
 
-std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
+std::string ShortenFileName(android::StringPiece file_path, int output_length) {
   std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
   std::string result = "";
   // Convert to (modified) base64 so that it is a proper file path.
@@ -58,9 +58,9 @@
   }
 }
 
-std::string GetShortenedPath(const android::StringPiece& shortened_filename,
-                             const android::StringPiece& extension, int collision_count) {
-  std::string shortened_path = "res/" + shortened_filename.to_string();
+std::string GetShortenedPath(android::StringPiece shortened_filename,
+                             android::StringPiece extension, int collision_count) {
+  std::string shortened_path = std::string("res/") += shortened_filename;
   if (collision_count > 0) {
     shortened_path += std::to_string(collision_count);
   }
diff --git a/tools/aapt2/optimize/VersionCollapser_test.cpp b/tools/aapt2/optimize/VersionCollapser_test.cpp
index aa0d0c0..18dcd6b 100644
--- a/tools/aapt2/optimize/VersionCollapser_test.cpp
+++ b/tools/aapt2/optimize/VersionCollapser_test.cpp
@@ -23,7 +23,7 @@
 namespace aapt {
 
 static std::unique_ptr<ResourceTable> BuildTableWithConfigs(
-    const StringPiece& name, std::initializer_list<std::string> list) {
+    StringPiece name, std::initializer_list<std::string> list) {
   test::ResourceTableBuilder builder;
   for (const std::string& item : list) {
     builder.AddSimple(name, test::ParseConfigOrDie(item));
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 92b45c3..bca62da 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -218,7 +218,7 @@
   return symbol;
 }
 
-bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
+bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) {
   TRACE_CALL();
   if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) {
     apk_assets_.push_back(std::move(apk));
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index c17837c..b09ff70 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -192,7 +192,7 @@
  public:
   AssetManagerSymbolSource() = default;
 
-  bool AddAssetPath(const android::StringPiece& path);
+  bool AddAssetPath(android::StringPiece path);
   std::map<size_t, std::string> GetAssignedPackageIds() const;
   bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
 
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 30336e2..65f63dc 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -34,61 +34,53 @@
 namespace aapt {
 namespace test {
 
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
-                                                      const ResourceId& id) {
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ResourceId& id) {
   return AddValue(name, id, util::make_unique<Id>());
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name,
                                                       const ConfigDescription& config,
                                                       const ResourceId& id) {
   return AddValue(name, config, id, util::make_unique<Id>());
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
-                                                         const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, StringPiece ref) {
   return AddReference(name, {}, ref);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
-                                                         const ResourceId& id,
-                                                         const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, const ResourceId& id,
+                                                         StringPiece ref) {
   return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name,
-                                                      const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, StringPiece str) {
   return AddString(name, {}, str);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
-                                                      const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
+                                                      StringPiece str) {
   return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
                                                       const ConfigDescription& config,
-                                                      const StringPiece& str) {
+                                                      StringPiece str) {
   return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
                                                              io::IFile* file) {
   return AddFileReference(name, {}, path, file);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const ResourceId& id,
-                                                             const StringPiece& path,
-                                                             io::IFile* file) {
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, const ResourceId& id,
+                                                             StringPiece path, io::IFile* file) {
   auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
   file_ref->file = file;
   return AddValue(name, id, std::move(file_ref));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
                                                              const ConfigDescription& config,
                                                              io::IFile* file) {
   auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
@@ -96,17 +88,17 @@
   return AddValue(name, config, {}, std::move(file_ref));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
                                                      std::unique_ptr<Value> value) {
   return AddValue(name, {}, std::move(value));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
   return AddValue(name, {}, id, std::move(value));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
                                                      const ConfigDescription& config,
                                                      const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
@@ -121,8 +113,7 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
-                                                           const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(StringPiece name, const ResourceId& id,
                                                            Visibility::Level level,
                                                            bool allow_new) {
   ResourceName res_name = ParseNameOrDie(name);
@@ -136,9 +127,8 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(StringPiece name,
                                                            const OverlayableItem& overlayable) {
-
   ResourceName res_name = ParseNameOrDie(name);
   CHECK(table_->AddResource(
       NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(),
@@ -159,8 +149,7 @@
   return std::move(table_);
 }
 
-std::unique_ptr<Reference> BuildReference(const StringPiece& ref,
-                                          const std::optional<ResourceId>& id) {
+std::unique_ptr<Reference> BuildReference(StringPiece ref, const std::optional<ResourceId>& id) {
   std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref));
   reference->id = id;
   return reference;
@@ -188,7 +177,7 @@
   return *this;
 }
 
-AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
+AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) {
   attr_->symbols.push_back(
       Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
   return *this;
@@ -198,17 +187,17 @@
   return std::move(attr_);
 }
 
-StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) {
+StyleBuilder& StyleBuilder::SetParent(StringPiece str) {
   style_->parent = Reference(ParseNameOrDie(str));
   return *this;
 }
 
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, std::unique_ptr<Item> value) {
   style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
   return *this;
 }
 
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id,
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, const ResourceId& id,
                                     std::unique_ptr<Item> value) {
   AddItem(str, std::move(value));
   style_->entries.back().key.id = id;
@@ -219,8 +208,7 @@
   return std::move(style_);
 }
 
-StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str,
-                                            const std::optional<ResourceId>& id) {
+StyleableBuilder& StyleableBuilder::AddItem(StringPiece str, const std::optional<ResourceId>& id) {
   styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
   styleable_->entries.back().id = id;
   return *this;
@@ -230,7 +218,7 @@
   return std::move(styleable_);
 }
 
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) {
+std::unique_ptr<xml::XmlResource> BuildXmlDom(StringPiece str) {
   std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
   input.append(str.data(), str.size());
   StringInputStream in(input);
@@ -241,7 +229,7 @@
 }
 
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
-                                                            const StringPiece& str) {
+                                                            StringPiece str) {
   std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
   doc->file.name.package = context->GetCompilationPackage();
   return doc;
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 780bd0d..f03d6fc 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -38,40 +38,35 @@
  public:
   ResourceTableBuilder() = default;
 
-  ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});
-  ResourceTableBuilder& AddSimple(const android::StringPiece& name,
+  ResourceTableBuilder& AddSimple(android::StringPiece name, const ResourceId& id = {});
+  ResourceTableBuilder& AddSimple(android::StringPiece name,
                                   const android::ConfigDescription& config,
                                   const ResourceId& id = {});
-  ResourceTableBuilder& AddReference(const android::StringPiece& name,
-                                     const android::StringPiece& ref);
-  ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
-                                     const android::StringPiece& ref);
-  ResourceTableBuilder& AddString(const android::StringPiece& name,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddReference(android::StringPiece name, android::StringPiece ref);
+  ResourceTableBuilder& AddReference(android::StringPiece name, const ResourceId& id,
+                                     android::StringPiece ref);
+  ResourceTableBuilder& AddString(android::StringPiece name, android::StringPiece str);
+  ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
+                                  android::StringPiece str);
+  ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
                                   const android::ConfigDescription& config,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path,
+                                  android::StringPiece str);
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
                                          io::IFile* file = nullptr);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
-                                         const android::StringPiece& path,
-                                         io::IFile* file = nullptr);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path,
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, const ResourceId& id,
+                                         android::StringPiece path, io::IFile* file = nullptr);
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
                                          const android::ConfigDescription& config,
                                          io::IFile* file = nullptr);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddValue(android::StringPiece name, std::unique_ptr<Value> value);
+  ResourceTableBuilder& AddValue(android::StringPiece name, const ResourceId& id,
                                  std::unique_ptr<Value> value);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name,
-                                 const android::ConfigDescription& config,
-                                 const ResourceId& id, std::unique_ptr<Value> value);
-  ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddValue(android::StringPiece name,
+                                 const android::ConfigDescription& config, const ResourceId& id,
+                                 std::unique_ptr<Value> value);
+  ResourceTableBuilder& SetSymbolState(android::StringPiece name, const ResourceId& id,
                                        Visibility::Level level, bool allow_new = false);
-  ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
+  ResourceTableBuilder& SetOverlayable(android::StringPiece name,
                                        const OverlayableItem& overlayable);
   ResourceTableBuilder& Add(NewResource&& res);
 
@@ -84,7 +79,7 @@
   std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
 };
 
-std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
+std::unique_ptr<Reference> BuildReference(android::StringPiece ref,
                                           const std::optional<ResourceId>& id = {});
 std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data);
 
@@ -101,7 +96,7 @@
     return *this;
   }
 
-  ValueBuilder& SetComment(const android::StringPiece& str) {
+  ValueBuilder& SetComment(android::StringPiece str) {
     value_->SetComment(str);
     return *this;
   }
@@ -121,7 +116,7 @@
   AttributeBuilder();
   AttributeBuilder& SetTypeMask(uint32_t typeMask);
   AttributeBuilder& SetWeak(bool weak);
-  AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
+  AttributeBuilder& AddItem(android::StringPiece name, uint32_t value);
   std::unique_ptr<Attribute> Build();
 
  private:
@@ -133,9 +128,9 @@
 class StyleBuilder {
  public:
   StyleBuilder() = default;
-  StyleBuilder& SetParent(const android::StringPiece& str);
-  StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value);
-  StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id,
+  StyleBuilder& SetParent(android::StringPiece str);
+  StyleBuilder& AddItem(android::StringPiece str, std::unique_ptr<Item> value);
+  StyleBuilder& AddItem(android::StringPiece str, const ResourceId& id,
                         std::unique_ptr<Item> value);
   std::unique_ptr<Style> Build();
 
@@ -148,8 +143,7 @@
 class StyleableBuilder {
  public:
   StyleableBuilder() = default;
-  StyleableBuilder& AddItem(const android::StringPiece& str,
-                            const std::optional<ResourceId>& id = {});
+  StyleableBuilder& AddItem(android::StringPiece str, const std::optional<ResourceId>& id = {});
   std::unique_ptr<Styleable> Build();
 
  private:
@@ -158,9 +152,9 @@
   std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
 };
 
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str);
+std::unique_ptr<xml::XmlResource> BuildXmlDom(android::StringPiece str);
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
-                                                            const android::StringPiece& str);
+                                                            android::StringPiece str);
 
 class ArtifactBuilder {
  public:
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index eca0c1c..cdf24534 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -44,10 +44,9 @@
 }
 
 template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
-                                          const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
                                           const ConfigDescription& config,
-                                          const android::StringPiece& product) {
+                                          android::StringPiece product) {
   std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
   if (result) {
     ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 3f28361..83a0f3f 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -39,22 +39,22 @@
 
 android::IDiagnostics* GetDiagnostics();
 
-inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
+inline ResourceName ParseNameOrDie(android::StringPiece str) {
   ResourceNameRef ref;
   CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str;
   return ref.ToResourceName();
 }
 
-inline android::ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
-    android::ConfigDescription config;
+inline android::ConfigDescription ParseConfigOrDie(android::StringPiece str) {
+  android::ConfigDescription config;
   CHECK(android::ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
   return config;
 }
 
 template <typename T = Value>
-T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfigAndProduct(ResourceTable* table, android::StringPiece res_name,
                                const android::ConfigDescription& config,
-                               const android::StringPiece& product) {
+                               android::StringPiece product) {
   std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
   if (result) {
     ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
@@ -66,25 +66,25 @@
 }
 
 template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
-                                          const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
                                           const android::ConfigDescription& config,
-                                          const android::StringPiece& product);
+                                          android::StringPiece product);
 
 template <typename T = Value>
-T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfig(ResourceTable* table, android::StringPiece res_name,
                      const android::ConfigDescription& config) {
   return GetValueForConfigAndProduct<T>(table, res_name, config, {});
 }
 
 template <typename T = Value>
-T* GetValue(ResourceTable* table, const android::StringPiece& res_name) {
+T* GetValue(ResourceTable* table, android::StringPiece res_name) {
   return GetValueForConfig<T>(table, res_name, {});
 }
 
 class TestFile : public io::IFile {
  public:
-  explicit TestFile(const android::StringPiece& path) : source_(path) {}
+  explicit TestFile(android::StringPiece path) : source_(path) {
+  }
 
   std::unique_ptr<io::IData> OpenAsData() override {
     return {};
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4e4973e..c5331fb 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -52,8 +52,8 @@
     return compilation_package_.value();
   }
 
-  void SetCompilationPackage(const android::StringPiece& package) {
-    compilation_package_ = package.to_string();
+  void SetCompilationPackage(android::StringPiece package) {
+    compilation_package_ = std::string(package);
   }
 
   uint8_t GetPackageId() override {
@@ -111,8 +111,8 @@
     return *this;
   }
 
-  ContextBuilder& SetCompilationPackage(const android::StringPiece& package) {
-    context_->compilation_package_ = package.to_string();
+  ContextBuilder& SetCompilationPackage(android::StringPiece package) {
+    context_->compilation_package_ = std::string(package);
     return *this;
   }
 
@@ -149,7 +149,7 @@
 
 class StaticSymbolSourceBuilder {
  public:
-  StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id,
+  StaticSymbolSourceBuilder& AddPublicSymbol(android::StringPiece name, ResourceId id,
                                              std::unique_ptr<Attribute> attr = {}) {
     std::unique_ptr<SymbolTable::Symbol> symbol =
         util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
@@ -159,7 +159,7 @@
     return *this;
   }
 
-  StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id,
+  StaticSymbolSourceBuilder& AddSymbol(android::StringPiece name, ResourceId id,
                                        std::unique_ptr<Attribute> attr = {}) {
     std::unique_ptr<SymbolTable::Symbol> symbol =
         util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index dbc0e36..428372f 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -38,8 +38,8 @@
 
 const char* CommandTestFixture::kDefaultPackageName = "com.aapt.command.test";
 
-void ClearDirectory(const android::StringPiece& path) {
-  const std::string root_dir = path.to_string();
+void ClearDirectory(android::StringPiece path) {
+  const std::string root_dir(path);
   std::unique_ptr<DIR, decltype(closedir)*> dir(opendir(root_dir.data()), closedir);
   if (!dir) {
     StdErrDiagnostics().Error(android::DiagMessage()
@@ -91,8 +91,7 @@
 }
 
 bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
-                                     const android::StringPiece& out_dir,
-                                     android::IDiagnostics* diag) {
+                                     android::StringPiece out_dir, android::IDiagnostics* diag) {
   WriteFile(path, contents);
   CHECK(file::mkdirs(out_dir.data()));
   return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
@@ -113,8 +112,8 @@
   return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
 }
 
-bool CommandTestFixture::Link(const std::vector<std::string>& args,
-                              const android::StringPiece& flat_dir, android::IDiagnostics* diag) {
+bool CommandTestFixture::Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
+                              android::IDiagnostics* diag) {
   std::vector<android::StringPiece> link_args;
   for(const std::string& arg : args) {
     link_args.emplace_back(arg);
@@ -148,7 +147,7 @@
 }
 
 std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
-                                                              const android::StringPiece& path) {
+                                                              android::StringPiece path) {
   return apk
       ->GetFileCollection()
       ->FindFile(path)
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 61403b7..ba4a734 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -48,7 +48,7 @@
   // Retrieves the absolute path of the specified relative path in the test directory. Directories
   // should be separated using forward slashes ('/'), and these slashes will be translated to
   // backslashes when running Windows tests.
-  std::string GetTestPath(const android::StringPiece& path) {
+  std::string GetTestPath(android::StringPiece path) {
     std::string base = temp_dir_;
     for (android::StringPiece part : util::Split(path, '/')) {
       file::AppendPath(&base, part);
@@ -73,22 +73,21 @@
   // Wries the contents of the file to the specified path. The file is compiled and the flattened
   // file is written to the out directory.
   bool CompileFile(const std::string& path, const std::string& contents,
-                   const android::StringPiece& flat_out_dir, android::IDiagnostics* diag);
+                   android::StringPiece flat_out_dir, android::IDiagnostics* diag);
 
   // Executes the link command with the specified arguments.
   bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag);
 
   // Executes the link command with the specified arguments. The flattened files residing in the
   // flat directory will be added to the link command as file arguments.
-  bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir,
+  bool Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
             android::IDiagnostics* diag);
 
   // Creates a minimal android manifest within the test directory and returns the file path.
   std::string GetDefaultManifest(const char* package_name = kDefaultPackageName);
 
   // Returns pointer to data inside APK files
-  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
-                                            const android::StringPiece& path);
+  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, android::StringPiece path);
 
   // Asserts that loading the tree from the specified file in the apk succeeds.
   void AssertLoadXml(LoadedApk* apk, const io::IData* data,
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index 243800c..8e491ac 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -26,7 +26,7 @@
 namespace aapt {
 namespace text {
 
-Printer& Printer::Println(const StringPiece& str) {
+Printer& Printer::Println(StringPiece str) {
   Print(str);
   return Print("\n");
 }
@@ -35,7 +35,7 @@
   return Print("\n");
 }
 
-Printer& Printer::Print(const StringPiece& str) {
+Printer& Printer::Print(StringPiece str) {
   if (error_) {
     return *this;
   }
@@ -47,7 +47,7 @@
     const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n');
 
     // We will copy the string up until the next new-line (or end of string).
-    const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter);
+    const StringPiece str_to_copy(remaining_str_begin, new_line_iter - remaining_str_begin);
     if (!str_to_copy.empty()) {
       if (needs_indent_) {
         for (int i = 0; i < indent_level_; i++) {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index f399f8e..f7ad98b 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -31,8 +31,8 @@
   explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
   }
 
-  Printer& Print(const ::android::StringPiece& str);
-  Printer& Println(const ::android::StringPiece& str);
+  Printer& Print(android::StringPiece str);
+  Printer& Println(android::StringPiece str);
   Printer& Println();
 
   void Indent();
diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp
index 3735b3e..5e25be3 100644
--- a/tools/aapt2/text/Unicode.cpp
+++ b/tools/aapt2/text/Unicode.cpp
@@ -77,7 +77,7 @@
          (codepoint == 0x3000);
 }
 
-bool IsJavaIdentifier(const StringPiece& str) {
+bool IsJavaIdentifier(StringPiece str) {
   Utf8Iterator iter(str);
 
   // Check the first character.
@@ -99,7 +99,7 @@
   return true;
 }
 
-bool IsValidResourceEntryName(const StringPiece& str) {
+bool IsValidResourceEntryName(StringPiece str) {
   Utf8Iterator iter(str);
 
   // Check the first character.
diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h
index 546714e..ab3e82b 100644
--- a/tools/aapt2/text/Unicode.h
+++ b/tools/aapt2/text/Unicode.h
@@ -46,11 +46,11 @@
 
 // Returns true if the UTF8 string can be used as a Java identifier.
 // NOTE: This does not check against the set of reserved Java keywords.
-bool IsJavaIdentifier(const android::StringPiece& str);
+bool IsJavaIdentifier(android::StringPiece str);
 
 // Returns true if the UTF8 string can be used as the entry name of a resource name.
 // This is the `entry` part of package:type/entry.
-bool IsValidResourceEntryName(const android::StringPiece& str);
+bool IsValidResourceEntryName(android::StringPiece str);
 
 }  // namespace text
 }  // namespace aapt
diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp
index 20b9073..0bd8a37 100644
--- a/tools/aapt2/text/Utf8Iterator.cpp
+++ b/tools/aapt2/text/Utf8Iterator.cpp
@@ -24,7 +24,7 @@
 namespace aapt {
 namespace text {
 
-Utf8Iterator::Utf8Iterator(const StringPiece& str)
+Utf8Iterator::Utf8Iterator(StringPiece str)
     : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) {
   DoNext();
 }
diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h
index 9318401..2bba198 100644
--- a/tools/aapt2/text/Utf8Iterator.h
+++ b/tools/aapt2/text/Utf8Iterator.h
@@ -25,7 +25,7 @@
 
 class Utf8Iterator {
  public:
-  explicit Utf8Iterator(const android::StringPiece& str);
+  explicit Utf8Iterator(android::StringPiece str);
 
   bool HasNext() const;
 
diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp
index b4b31d9..da53739 100644
--- a/tools/aapt2/trace/TraceBuffer.cpp
+++ b/tools/aapt2/trace/TraceBuffer.cpp
@@ -103,7 +103,7 @@
   s << tag;
   s << " ";
   for (auto& arg : args) {
-    s << arg.to_string();
+    s << arg;
     s << " ";
   }
   tracebuffer::Add(s.str(), tracebuffer::kBegin);
@@ -124,7 +124,7 @@
   s << tag;
   s << " ";
   for (auto& arg : args) {
-    s << arg.to_string();
+    s << arg;
     s << " ";
   }
   tracebuffer::Add(s.str(), tracebuffer::kBegin);
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 5d5b7cd..93c1b61 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -139,7 +139,7 @@
   return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST;
 }
 
-StringPiece GetStem(const StringPiece& path) {
+StringPiece GetStem(StringPiece path) {
   const char* start = path.begin();
   const char* end = path.end();
   for (const char* current = end - 1; current != start - 1; --current) {
@@ -150,7 +150,7 @@
   return {};
 }
 
-StringPiece GetFilename(const StringPiece& path) {
+StringPiece GetFilename(StringPiece path) {
   const char* end = path.end();
   const char* last_dir_sep = path.begin();
   for (const char* c = path.begin(); c != end; ++c) {
@@ -161,7 +161,7 @@
   return StringPiece(last_dir_sep, end - last_dir_sep);
 }
 
-StringPiece GetExtension(const StringPiece& path) {
+StringPiece GetExtension(StringPiece path) {
   StringPiece filename = GetFilename(path);
   const char* const end = filename.end();
   const char* c = std::find(filename.begin(), end, '.');
@@ -171,7 +171,7 @@
   return {};
 }
 
-bool IsHidden(const android::StringPiece& path) {
+bool IsHidden(android::StringPiece path) {
   return util::StartsWith(GetFilename(path), ".");
 }
 
@@ -193,16 +193,16 @@
   if (args.empty()) {
     return "";
   }
-  std::string out = args[0].to_string();
+  std::string out{args[0]};
   for (int i = 1; i < args.size(); i++) {
     file::AppendPath(&out, args[i]);
   }
   return out;
 }
 
-std::string PackageToPath(const StringPiece& package) {
+std::string PackageToPath(StringPiece package) {
   std::string out_path;
-  for (const StringPiece& part : util::Tokenize(package, '.')) {
+  for (StringPiece part : util::Tokenize(package, '.')) {
     AppendPath(&out_path, part);
   }
   return out_path;
@@ -241,10 +241,10 @@
   return std::move(filemap);
 }
 
-bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist,
                         std::string* out_error) {
   std::string contents;
-  if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+  if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
     if (out_error) {
       *out_error = "failed to read argument-list file";
     }
@@ -254,16 +254,16 @@
   for (StringPiece line : util::Tokenize(contents, ' ')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      out_arglist->push_back(line.to_string());
+      out_arglist->emplace_back(line);
     }
   }
   return true;
 }
 
-bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::string>* out_argset,
+bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset,
                            std::string* out_error) {
   std::string contents;
-  if(!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+  if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
     if (out_error) {
       *out_error = "failed to read argument-list file";
     }
@@ -273,13 +273,13 @@
   for (StringPiece line : util::Tokenize(contents, ' ')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      out_argset->insert(line.to_string());
+      out_argset->emplace(line);
     }
   }
   return true;
 }
 
-bool FileFilter::SetPattern(const StringPiece& pattern) {
+bool FileFilter::SetPattern(StringPiece pattern) {
   pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
   return true;
 }
@@ -343,10 +343,10 @@
   return true;
 }
 
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
                                                   android::IDiagnostics* diag,
                                                   const FileFilter* filter) {
-  const std::string root_dir = path.to_string();
+  const auto& root_dir = path;
   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
     diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir);
@@ -361,7 +361,7 @@
     }
 
     std::string file_name = entry->d_name;
-    std::string full_path = root_dir;
+    std::string full_path{root_dir};
     AppendPath(&full_path, file_name);
     const FileType file_type = GetFileType(full_path);
 
@@ -380,7 +380,7 @@
 
   // Now process subdirs.
   for (const std::string& subdir : subdirs) {
-    std::string full_subdir = root_dir;
+    std::string full_subdir{root_dir};
     AppendPath(&full_subdir, subdir);
     std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
     if (!subfiles) {
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index ee95712..42eeaf2 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -66,31 +66,31 @@
 bool mkdirs(const std::string& path);
 
 // Returns all but the last part of the path.
-android::StringPiece GetStem(const android::StringPiece& path);
+android::StringPiece GetStem(android::StringPiece path);
 
 // Returns the last part of the path with extension.
-android::StringPiece GetFilename(const android::StringPiece& path);
+android::StringPiece GetFilename(android::StringPiece path);
 
 // Returns the extension of the path. This is the entire string after the first '.' of the last part
 // of the path.
-android::StringPiece GetExtension(const android::StringPiece& path);
+android::StringPiece GetExtension(android::StringPiece path);
 
 // Returns whether or not the name of the file or directory is a hidden file name
-bool IsHidden(const android::StringPiece& path);
+bool IsHidden(android::StringPiece path);
 
 // Converts a package name (com.android.app) to a path: com/android/app
-std::string PackageToPath(const android::StringPiece& package);
+std::string PackageToPath(android::StringPiece package);
 
 // Creates a FileMap for the file at path.
 std::optional<android::FileMap> MmapPath(const std::string& path, std::string* out_error);
 
 // Reads the file at path and appends each line to the outArgList vector.
-bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(android::StringPiece path, std::vector<std::string>* out_arglist,
                         std::string* out_error);
 
 // Reads the file at path and appends each line to the outargset set.
-bool AppendSetArgsFromFile(const android::StringPiece& path,
-                        std::unordered_set<std::string>* out_argset, std::string* out_error);
+bool AppendSetArgsFromFile(android::StringPiece path, std::unordered_set<std::string>* out_argset,
+                           std::string* out_error);
 
 // Filter that determines which resource files/directories are
 // processed by AAPT. Takes a pattern string supplied by the user.
@@ -112,7 +112,7 @@
   // - The special filenames "." and ".." are always ignored.
   // - Otherwise the full string is matched.
   // - match is not case-sensitive.
-  bool SetPattern(const android::StringPiece& pattern);
+  bool SetPattern(android::StringPiece pattern);
 
   // Applies the filter, returning true for pass, false for fail.
   bool operator()(const std::string& filename, FileType type) const;
@@ -126,7 +126,7 @@
 
 // Returns a list of files relative to the directory identified by `path`.
 // An optional FileFilter filters out any files that don't pass.
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
                                                   android::IDiagnostics* diag,
                                                   const FileFilter* filter = nullptr);
 
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 9b7ebdd..be87766 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -43,15 +43,14 @@
 // See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
 constexpr static const size_t kMaxPackageNameSize = 223;
 
-static std::vector<std::string> SplitAndTransform(
-    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, char (*f)(char)) {
   std::vector<std::string> parts;
   const StringPiece::const_iterator end = std::end(str);
   StringPiece::const_iterator start = std::begin(str);
   StringPiece::const_iterator current;
   do {
     current = std::find(start, end, sep);
-    parts.emplace_back(str.substr(start, current).to_string());
+    parts.emplace_back(start, current);
     if (f) {
       std::string& part = parts.back();
       std::transform(part.begin(), part.end(), part.begin(), f);
@@ -61,29 +60,29 @@
   return parts;
 }
 
-std::vector<std::string> Split(const StringPiece& str, char sep) {
+std::vector<std::string> Split(StringPiece str, char sep) {
   return SplitAndTransform(str, sep, nullptr);
 }
 
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
-  return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+  return SplitAndTransform(str, sep, [](char c) -> char { return ::tolower(c); });
 }
 
-bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
+bool StartsWith(StringPiece str, StringPiece prefix) {
   if (str.size() < prefix.size()) {
     return false;
   }
   return str.substr(0, prefix.size()) == prefix;
 }
 
-bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
+bool EndsWith(StringPiece str, StringPiece suffix) {
   if (str.size() < suffix.size()) {
     return false;
   }
   return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
 }
 
-StringPiece TrimLeadingWhitespace(const StringPiece& str) {
+StringPiece TrimLeadingWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -97,7 +96,7 @@
   return StringPiece(start, end - start);
 }
 
-StringPiece TrimTrailingWhitespace(const StringPiece& str) {
+StringPiece TrimTrailingWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -111,7 +110,7 @@
   return StringPiece(start, end - start);
 }
 
-StringPiece TrimWhitespace(const StringPiece& str) {
+StringPiece TrimWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -130,9 +129,9 @@
   return StringPiece(start, end - start);
 }
 
-static int IsJavaNameImpl(const StringPiece& str) {
+static int IsJavaNameImpl(StringPiece str) {
   int pieces = 0;
-  for (const StringPiece& piece : Tokenize(str, '.')) {
+  for (StringPiece piece : Tokenize(str, '.')) {
     pieces++;
     if (!text::IsJavaIdentifier(piece)) {
       return -1;
@@ -141,17 +140,17 @@
   return pieces;
 }
 
-bool IsJavaClassName(const StringPiece& str) {
+bool IsJavaClassName(StringPiece str) {
   return IsJavaNameImpl(str) >= 2;
 }
 
-bool IsJavaPackageName(const StringPiece& str) {
+bool IsJavaPackageName(StringPiece str) {
   return IsJavaNameImpl(str) >= 1;
 }
 
-static int IsAndroidNameImpl(const StringPiece& str) {
+static int IsAndroidNameImpl(StringPiece str) {
   int pieces = 0;
-  for (const StringPiece& piece : Tokenize(str, '.')) {
+  for (StringPiece piece : Tokenize(str, '.')) {
     if (piece.empty()) {
       return -1;
     }
@@ -173,15 +172,14 @@
   return pieces;
 }
 
-bool IsAndroidPackageName(const StringPiece& str) {
+bool IsAndroidPackageName(StringPiece str) {
   if (str.size() > kMaxPackageNameSize) {
     return false;
   }
   return IsAndroidNameImpl(str) > 1 || str == "android";
 }
 
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
-                           const android::StringPiece& shared_user_id) {
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id) {
   if (shared_user_id.size() > kMaxPackageNameSize) {
     return false;
   }
@@ -189,25 +187,24 @@
          package_name == "android";
 }
 
-bool IsAndroidSplitName(const StringPiece& str) {
+bool IsAndroidSplitName(StringPiece str) {
   return IsAndroidNameImpl(str) > 0;
 }
 
-std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package,
-                                                      const StringPiece& classname) {
+std::optional<std::string> GetFullyQualifiedClassName(StringPiece package, StringPiece classname) {
   if (classname.empty()) {
     return {};
   }
 
   if (util::IsJavaClassName(classname)) {
-    return classname.to_string();
+    return std::string(classname);
   }
 
   if (package.empty()) {
     return {};
   }
 
-  std::string result = package.to_string();
+  std::string result{package};
   if (classname.data()[0] != '.') {
     result += '.';
   }
@@ -251,7 +248,7 @@
   return static_cast<size_t>(c - start);
 }
 
-bool VerifyJavaStringFormat(const StringPiece& str) {
+bool VerifyJavaStringFormat(StringPiece str) {
   const char* c = str.begin();
   const char* const end = str.end();
 
@@ -341,7 +338,7 @@
   return true;
 }
 
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
   ssize_t utf16_length = utf8_to_utf16_length(
       reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
   if (utf16_length <= 0) {
@@ -381,7 +378,7 @@
   const char* end = str_.end();
   if (start == end) {
     end_ = true;
-    token_.assign(token_.end(), 0);
+    token_ = StringPiece(token_.end(), 0);
     return *this;
   }
 
@@ -389,12 +386,12 @@
   const char* current = start;
   while (current != end) {
     if (*current == separator_) {
-      token_.assign(start, current - start);
+      token_ = StringPiece(start, current - start);
       return *this;
     }
     ++current;
   }
-  token_.assign(start, end - start);
+  token_ = StringPiece(start, end - start);
   return *this;
 }
 
@@ -409,15 +406,17 @@
   return !(*this == rhs);
 }
 
-Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end)
-    : str_(s), separator_(sep), token_(tok), end_(end) {}
+Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end)
+    : str_(s), separator_(sep), token_(tok), end_(end) {
+}
 
-Tokenizer::Tokenizer(const StringPiece& str, char sep)
+Tokenizer::Tokenizer(StringPiece str, char sep)
     : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
-      end_(str, sep, StringPiece(str.end(), 0), true) {}
+      end_(str, sep, StringPiece(str.end(), 0), true) {
+}
 
-bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
-                             StringPiece* out_entry, StringPiece* out_suffix) {
+bool ExtractResFilePathParts(StringPiece path, StringPiece* out_prefix, StringPiece* out_entry,
+                             StringPiece* out_suffix) {
   const StringPiece res_prefix("res/");
   if (!StartsWith(path, res_prefix)) {
     return false;
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 8d3b413..40ff5b6 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -48,44 +48,44 @@
   T end;
 };
 
-std::vector<std::string> Split(const android::StringPiece& str, char sep);
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> Split(android::StringPiece str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
 
 // Returns true if the string starts with prefix.
-bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);
+bool StartsWith(android::StringPiece str, android::StringPiece prefix);
 
 // Returns true if the string ends with suffix.
-bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
+bool EndsWith(android::StringPiece str, android::StringPiece suffix);
 
 // Creates a new StringPiece that points to a substring of the original string without leading
 // whitespace.
-android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimLeadingWhitespace(android::StringPiece str);
 
 // Creates a new StringPiece that points to a substring of the original string without trailing
 // whitespace.
-android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimTrailingWhitespace(android::StringPiece str);
 
 // Creates a new StringPiece that points to a substring of the original string without leading or
 // trailing whitespace.
-android::StringPiece TrimWhitespace(const android::StringPiece& str);
+android::StringPiece TrimWhitespace(android::StringPiece str);
 
 // Tests that the string is a valid Java class name.
-bool IsJavaClassName(const android::StringPiece& str);
+bool IsJavaClassName(android::StringPiece str);
 
 // Tests that the string is a valid Java package name.
-bool IsJavaPackageName(const android::StringPiece& str);
+bool IsJavaPackageName(android::StringPiece str);
 
 // Tests that the string is a valid Android package name. More strict than a Java package name.
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
 // - Package must contain at least two components, unless it is 'android'.
 // - The maximum package name length is 223.
-bool IsAndroidPackageName(const android::StringPiece& str);
+bool IsAndroidPackageName(android::StringPiece str);
 
 // Tests that the string is a valid Android split name.
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
-bool IsAndroidSplitName(const android::StringPiece& str);
+bool IsAndroidSplitName(android::StringPiece str);
 
 // Tests that the string is a valid Android shared user id.
 // - First character of each component (separated by '.') must be an ASCII letter.
@@ -93,8 +93,7 @@
 // - Must contain at least two components, unless package name is 'android'.
 // - The maximum shared user id length is 223.
 // - Treat empty string as valid, it's the case of no shared user id.
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
-                           const android::StringPiece& shared_user_id);
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id);
 
 // Converts the class name to a fully qualified class name from the given
 // `package`. Ex:
@@ -103,8 +102,8 @@
 // .asdf        --> package.asdf
 // .a.b         --> package.a.b
 // asdf.adsf    --> asdf.adsf
-std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
-                                                      const android::StringPiece& class_name);
+std::optional<std::string> GetFullyQualifiedClassName(android::StringPiece package,
+                                                      android::StringPiece class_name);
 
 // Retrieves the formatted name of aapt2.
 const char* GetToolName();
@@ -152,16 +151,16 @@
 // explicitly specifying an index) when there are more than one argument. This is an error
 // because translations may rearrange the order of the arguments in the string, which will
 // break the string interpolation.
-bool VerifyJavaStringFormat(const android::StringPiece& str);
+bool VerifyJavaStringFormat(android::StringPiece str);
 
-bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces,
-                        std::string* out_str, std::string* out_error);
+bool AppendStyledString(android::StringPiece input, bool preserve_spaces, std::string* out_str,
+                        std::string* out_error);
 
 class StringBuilder {
  public:
   StringBuilder() = default;
 
-  StringBuilder& Append(const android::StringPiece& str);
+  StringBuilder& Append(android::StringPiece str);
   const std::string& ToString() const;
   const std::string& Error() const;
   bool IsEmpty() const;
@@ -229,7 +228,7 @@
    private:
     friend class Tokenizer;
 
-    iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end);
+    iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
 
     android::StringPiece str_;
     char separator_;
@@ -237,7 +236,7 @@
     bool end_;
   };
 
-  Tokenizer(const android::StringPiece& str, char sep);
+  Tokenizer(android::StringPiece str, char sep);
 
   iterator begin() const {
     return begin_;
@@ -252,7 +251,7 @@
   const iterator end_;
 };
 
-inline Tokenizer Tokenize(const android::StringPiece& str, char sep) {
+inline Tokenizer Tokenize(android::StringPiece str, char sep) {
   return Tokenizer(str, sep);
 }
 
@@ -263,7 +262,7 @@
 // Extracts ".xml" into outSuffix.
 //
 // Returns true if successful.
-bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
+bool ExtractResFilePathParts(android::StringPiece path, android::StringPiece* out_prefix,
                              android::StringPiece* out_entry, android::StringPiece* out_suffix);
 
 }  // namespace util
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 4ebcb11..15135690 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -84,6 +84,14 @@
   ASSERT_THAT(*iter, Eq(StringPiece()));
 }
 
+TEST(UtilTest, TokenizeNone) {
+  auto tokenizer = util::Tokenize(StringPiece("none"), '.');
+  auto iter = tokenizer.begin();
+  ASSERT_THAT(*iter, Eq("none"));
+  ++iter;
+  ASSERT_THAT(iter, Eq(tokenizer.end()));
+}
+
 TEST(UtilTest, IsJavaClassName) {
   EXPECT_TRUE(util::IsJavaClassName("android.test.Class"));
   EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner"));
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 9bdbd22..3ccbaa2 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -84,7 +84,7 @@
         error_msg << "unexpected element ";
         PrintElementToDiagMessage(child_el, &error_msg);
         error_msg << " found in ";
-        for (const StringPiece& element : *bread_crumb) {
+        for (StringPiece element : *bread_crumb) {
           error_msg << "<" << element << ">";
         }
         if (policy == XmlActionExecutorPolicy::kAllowListWarning) {
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index f51e8a4..8dea8ea 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -169,7 +169,7 @@
   stack->last_text_node = util::make_unique<Text>();
   stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
   stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
-  stack->last_text_node->text = str.to_string();
+  stack->last_text_node->text.assign(str);
 }
 
 static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
@@ -417,11 +417,11 @@
   children.insert(children.begin() + index, std::move(child));
 }
 
-Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) {
   return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
 }
 
-const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
+const Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) const {
   for (const auto& attr : attributes) {
     if (ns == attr.namespace_uri && name == attr.name) {
       return &attr;
@@ -430,7 +430,7 @@
   return nullptr;
 }
 
-void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) {
+void Element::RemoveAttribute(StringPiece ns, StringPiece name) {
   auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(),
     [&](const Attribute& attr) -> bool {
       return ns == attr.namespace_uri && name == attr.name;
@@ -439,34 +439,32 @@
   attributes.erase(new_attr_end, attributes.end());
 }
 
-Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindOrCreateAttribute(StringPiece ns, StringPiece name) {
   Attribute* attr = FindAttribute(ns, name);
   if (attr == nullptr) {
-    attributes.push_back(Attribute{ns.to_string(), name.to_string()});
+    attributes.push_back(Attribute{std::string(ns), std::string(name)});
     attr = &attributes.back();
   }
   return attr;
 }
 
-Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
+Element* Element::FindChild(StringPiece ns, StringPiece name) {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
-const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+const Element* Element::FindChild(StringPiece ns, StringPiece name) const {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
-Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
-                                         const StringPiece& attr_ns, const StringPiece& attr_name,
-                                         const StringPiece& attr_value) {
+Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, StringPiece attr_ns,
+                                         StringPiece attr_name, StringPiece attr_value) {
   return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
       ns, name, attr_ns, attr_name, attr_value));
 }
 
-const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
-                                               const StringPiece& attr_ns,
-                                               const StringPiece& attr_name,
-                                               const StringPiece& attr_value) const {
+const Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name,
+                                               StringPiece attr_ns, StringPiece attr_name,
+                                               StringPiece attr_value) const {
   for (const auto& child : children) {
     if (const Element* el = NodeCast<Element>(child.get())) {
       if (ns == el->namespace_uri && name == el->name) {
@@ -559,7 +557,7 @@
 }
 
 std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
-    const StringPiece& alias) const {
+    StringPiece alias) const {
   if (alias.empty()) {
     return ExtractedPackage{{}, false /*private*/};
   }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 5bc55b6..c253b0a 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -96,27 +96,22 @@
   void AppendChild(std::unique_ptr<Node> child);
   void InsertChild(size_t index, std::unique_ptr<Node> child);
 
-  Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
-  const Attribute* FindAttribute(const android::StringPiece& ns,
-                                 const android::StringPiece& name) const;
-  Attribute* FindOrCreateAttribute(const android::StringPiece& ns,
-                                   const android::StringPiece& name);
-  void RemoveAttribute(const android::StringPiece& ns,
-                       const android::StringPiece& name);
+  Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name);
+  const Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name) const;
+  Attribute* FindOrCreateAttribute(android::StringPiece ns, android::StringPiece name);
+  void RemoveAttribute(android::StringPiece ns, android::StringPiece name);
 
-  Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
-  const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+  Element* FindChild(android::StringPiece ns, android::StringPiece name);
+  const Element* FindChild(android::StringPiece ns, android::StringPiece name) const;
 
-  Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
-                                  const android::StringPiece& attr_ns,
-                                  const android::StringPiece& attr_name,
-                                  const android::StringPiece& attr_value);
+  Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+                                  android::StringPiece attr_ns, android::StringPiece attr_name,
+                                  android::StringPiece attr_value);
 
-  const Element* FindChildWithAttribute(const android::StringPiece& ns,
-                                        const android::StringPiece& name,
-                                        const android::StringPiece& attr_ns,
-                                        const android::StringPiece& attr_name,
-                                        const android::StringPiece& attr_value) const;
+  const Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+                                        android::StringPiece attr_ns,
+                                        android::StringPiece attr_name,
+                                        android::StringPiece attr_value) const;
 
   std::vector<Element*> GetChildElements();
 
@@ -235,8 +230,7 @@
  public:
   using Visitor::Visit;
 
-  std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const override;
+  std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
 
  protected:
   PackageAwareVisitor() = default;
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index bfa0749..d79446b 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -140,8 +140,7 @@
   return event_queue_.front().data2;
 }
 
-std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(
-    const StringPiece& alias) const {
+std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(StringPiece alias) const {
   if (alias.empty()) {
     return ExtractedPackage{{}, false /*private*/};
   }
@@ -307,7 +306,7 @@
                                       parser->depth_ });
 }
 
-std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) {
+std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) {
   auto iter = parser->FindAttribute("", name);
   if (iter != parser->end_attributes()) {
     return StringPiece(util::TrimWhitespace(iter->value));
@@ -315,8 +314,7 @@
   return {};
 }
 
-std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
-                                                 const StringPiece& name) {
+std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, StringPiece name) {
   auto iter = parser->FindAttribute("", name);
   if (iter != parser->end_attributes()) {
     StringPiece trimmed = util::TrimWhitespace(iter->value);
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index ab34772..fe4cd01 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -120,8 +120,7 @@
    * If xmlns:app="http://schemas.android.com/apk/res-auto", then
    * 'package' will be set to 'defaultPackage'.
    */
-  std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const override;
+  std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
 
   struct PackageDecl {
     std::string prefix;
@@ -194,7 +193,7 @@
  * Finds the attribute in the current element within the global namespace.
  */
 std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser,
-                                                  const android::StringPiece& name);
+                                                  android::StringPiece name);
 
 /**
  * Finds the attribute in the current element within the global namespace. The
@@ -202,7 +201,7 @@
  * must not be the empty string.
  */
 std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
-                                                          const android::StringPiece& name);
+                                                          android::StringPiece name);
 
 //
 // Implementation
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index 114b5ba..709755e 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -27,7 +27,7 @@
 namespace aapt {
 namespace xml {
 
-std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+std::string BuildPackageNamespace(StringPiece package, bool private_reference) {
   std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
   result.append(package.data(), package.size());
   return result;
@@ -41,7 +41,7 @@
     if (package.empty()) {
       return {};
     }
-    return ExtractedPackage{package.to_string(), false /* is_private */};
+    return ExtractedPackage{std::string(package), false /* is_private */};
 
   } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
     StringPiece schema_prefix = kSchemaPrivatePrefix;
@@ -50,7 +50,7 @@
     if (package.empty()) {
       return {};
     }
-    return ExtractedPackage{package.to_string(), true /* is_private */};
+    return ExtractedPackage{std::string(package), true /* is_private */};
 
   } else if (namespace_uri == kSchemaAuto) {
     return ExtractedPackage{std::string(), true /* is_private */};
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 1ab05a9..ad676ca 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -59,8 +59,7 @@
 //
 // If privateReference == true, the package will be of the form:
 //   http://schemas.android.com/apk/prv/res/<package>
-std::string BuildPackageNamespace(const android::StringPiece& package,
-                                  bool private_reference = false);
+std::string BuildPackageNamespace(android::StringPiece package, bool private_reference = false);
 
 // Interface representing a stack of XML namespace declarations. When looking up the package for a
 // namespace prefix, the stack is checked from top to bottom.
@@ -69,7 +68,7 @@
 
   // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
   virtual std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const = 0;
+      android::StringPiece alias) const = 0;
 };
 
 // Helper function for transforming the original Reference inRef to a fully qualified reference
diff --git a/tools/fonts/font-scaling-array-generator.js b/tools/fonts/font-scaling-array-generator.js
new file mode 100644
index 0000000..9754697
--- /dev/null
+++ b/tools/fonts/font-scaling-array-generator.js
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+/**
+  Generates arrays for non-linear font scaling, to be pasted into
+  frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java
+
+  To use:
+    `node font-scaling-array-generator.js`
+    or just open a browser, open DevTools, and paste into the Console.
+*/
+
+/**
+ * Modify this to match your packages/apps/Settings/res/arrays.xml#entryvalues_font_size
+ * array so that all possible scales are generated.
+ */
+const scales = [1.15, 1.30, 1.5, 1.8, 2];
+
+const commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100];
+
+/**
+ * Enum for GENERATION_STYLE which determines how to generate the arrays.
+ */
+const GenerationStyle = {
+  /**
+   * Interpolates between hand-tweaked curves. This is the best option and
+   * shouldn't require any additional tweaking.
+   */
+  CUSTOM_TWEAKED: 'CUSTOM_TWEAKED',
+
+  /**
+   * Uses a curve equation that is mostly correct, but will need manual tweaking
+   * at some scales.
+   */
+  CURVE: 'CURVE',
+
+  /**
+   * Uses straight linear multiplication. Good starting point for manual
+   * tweaking.
+   */
+  LINEAR: 'LINEAR'
+}
+
+/**
+ * Determines how arrays are generated. Must be one of the GenerationStyle
+ * values.
+ */
+const GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED;
+
+// These are hand-tweaked curves from which we will derive the other
+// interstitial curves using linear interpolation, in the case of using
+// GenerationStyle.CUSTOM_TWEAKED.
+const interpolationTargets = {
+  1.0: commonSpSizes,
+  1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100],
+  2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100]
+};
+
+/**
+ * Interpolate a value with specified extrema, to a new value between new
+ * extrema.
+ *
+ * @param value the current value
+ * @param inputMin minimum the input value can reach
+ * @param inputMax maximum the input value can reach
+ * @param outputMin minimum the output value can reach
+ * @param outputMax maximum the output value can reach
+ */
+function map(value, inputMin, inputMax, outputMin, outputMax) {
+  return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
+}
+
+/***
+ * Interpolate between values a and b.
+ */
+function lerp(a, b, fraction) {
+  return (a * (1.0 - fraction)) + (b * fraction);
+}
+
+function generateRatios(scale) {
+  // Find the best two arrays to interpolate between.
+  let startTarget, endTarget;
+  let startTargetScale, endTargetScale;
+  const targetScales = Object.keys(interpolationTargets).sort();
+  for (let i = 0; i < targetScales.length - 1; i++) {
+    const targetScaleKey = targetScales[i];
+    const targetScale = parseFloat(targetScaleKey, 10);
+    const startTargetScaleKey = targetScaleKey;
+    const endTargetScaleKey = targetScales[i + 1];
+
+    if (scale < parseFloat(startTargetScaleKey, 10)) {
+      break;
+    }
+
+    startTargetScale = parseFloat(startTargetScaleKey, 10);
+    endTargetScale = parseFloat(endTargetScaleKey, 10);
+    startTarget = interpolationTargets[startTargetScaleKey];
+    endTarget = interpolationTargets[endTargetScaleKey];
+  }
+  const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1);
+
+  return commonSpSizes.map((sp, i) => {
+    const originalSizeDp = sp;
+    let newSizeDp;
+    switch (GENERATION_STYLE) {
+      case GenerationStyle.CUSTOM_TWEAKED:
+        newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress);
+        break;
+      case GenerationStyle.CURVE: {
+        let coeff1;
+        let coeff2;
+        if (scale < 1) {
+          // \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x
+          coeff1 = -5;
+          coeff2 = scale;
+        } else {
+          // (1.22^{-\left(x-10\right)}+1\right)\cdot x
+          coeff1 = map(scale, 1, 2, 2, 8);
+          coeff2 = 1;
+        }
+        newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp);
+        break;
+      }
+      case GenerationStyle.LINEAR:
+        newSizeDp = originalSizeDp * scale;
+        break;
+      default:
+        throw new Error('Invalid GENERATION_STYLE');
+    }
+    return {
+      fromSp: sp,
+      toDp: newSizeDp
+    }
+  });
+}
+
+const scaleArrays =
+    scales
+        .map(scale => {
+          const scaleString = (scale * 100).toFixed(0);
+          return {
+            scale,
+            name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent`
+          }
+        })
+        .map(scaleArray => {
+          const items = generateRatios(scaleArray.scale);
+
+          return {
+            ...scaleArray,
+            items
+          }
+        });
+
+function formatDigit(d) {
+  const twoSignificantDigits = Math.round(d * 100) / 100;
+  return String(twoSignificantDigits).padStart(4, ' ');
+}
+
+console.log(
+    '' +
+    scaleArrays.reduce(
+        (previousScaleArray, currentScaleArray) => {
+          const itemsFromSp = currentScaleArray.items.map(d => d.fromSp)
+                                .map(formatDigit)
+                                .join('f, ');
+          const itemsToDp = currentScaleArray.items.map(d => d.toDp)
+                                .map(formatDigit)
+                                .join('f, ');
+
+          return previousScaleArray + `
+        put(
+                /* scaleKey= */ ${currentScaleArray.scale}f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {${itemsFromSp}},
+                        /* toDp=   */
+                        new float[] {${itemsToDp}})
+        );
+     `;
+        },
+        ''));
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
deleted file mode 100644
index 2c53f39..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
+++ /dev/null
@@ -1,158 +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.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UIfExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UQualifiedReferenceExpression
-
-/**
- * Looks for methods implementing generated AIDL interface stubs
- * that can have simple permission checks migrated to
- * @EnforcePermission annotations
- *
- * TODO: b/242564870 (enable parse and autoFix of .aidl files)
- */
-@Suppress("UnstableApiUsage")
-class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
-        listOf(UMethod::class.java)
-
-    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
-    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
-        override fun visitMethod(node: UMethod) {
-            val interfaceName = getContainingAidlInterface(node)
-                .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
-            val body = (node.uastBody as? UBlockExpression) ?: return
-            val fix = accumulateSimplePermissionCheckFixes(body) ?: return
-
-            val javaRemoveFixes = fix.locations.map {
-                fix()
-                    .replace()
-                    .reformat(true)
-                    .range(it)
-                    .with("")
-                    .autoFix()
-                    .build()
-            }
-
-            val javaAnnotateFix = fix()
-                .annotate(fix.annotation)
-                .range(context.getLocation(node))
-                .autoFix()
-                .build()
-
-            val message =
-                "$interfaceName permission check can be converted to @EnforcePermission annotation"
-
-            context.report(
-                ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
-                fix.locations.last(),
-                message,
-                fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
-            )
-        }
-
-        /**
-         * Walk the expressions in the method, looking for simple permission checks.
-         *
-         * If a single permission check is found at the beginning of the method,
-         * this should be migrated to @EnforcePermission(value).
-         *
-         * If multiple consecutive permission checks are found,
-         * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
-         *
-         * As soon as something other than a permission check is encountered, stop looking,
-         * as some other business logic is happening that prevents an automated fix.
-         */
-        private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
-                EnforcePermissionFix? {
-            val singleFixes = mutableListOf<EnforcePermissionFix>()
-            for (expression in methodBody.expressions) {
-                singleFixes.add(getPermissionCheckFix(expression) ?: break)
-            }
-            return when (singleFixes.size) {
-                0 -> null
-                1 -> singleFixes[0]
-                else -> EnforcePermissionFix.compose(singleFixes)
-            }
-        }
-
-        /**
-         * If an expression boils down to a permission check, return
-         * the helper for creating a lint auto fix to @EnforcePermission
-         */
-        private fun getPermissionCheckFix(startingExpression: UElement?):
-                EnforcePermissionFix? {
-            return when (startingExpression) {
-                is UQualifiedReferenceExpression -> getPermissionCheckFix(
-                    startingExpression.selector
-                )
-
-                is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
-
-                is UCallExpression -> return EnforcePermissionFix
-                            .fromCallExpression(context, startingExpression)
-
-                else -> null
-            }
-        }
-    }
-
-    companion object {
-
-        private val EXPLANATION = """
-            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
-            annotation to declare the permissions to be enforced.  The verification code is then
-            generated by the AIDL compiler, which also takes care of annotating the generated java
-            code.
-
-            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
-            It also enables easier auditing and review.
-
-            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
-        """.trimIndent()
-
-        @JvmField
-        val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
-            id = "UseEnforcePermissionAnnotation",
-            briefDescription = "Manual permission check can be @EnforcePermission annotation",
-            explanation = EXPLANATION,
-            category = Category.SECURITY,
-            priority = 5,
-            severity = Severity.WARNING,
-            implementation = Implementation(
-                ManualPermissionCheckDetector::class.java,
-                Scope.JAVA_FILE_SCOPE
-            ),
-            enabledByDefault = false, // TODO: enable once b/241171714 is resolved
-        )
-    }
-}
diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp
new file mode 100644
index 0000000..898f88b
--- /dev/null
+++ b/tools/lint/common/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+    name: "AndroidCommonLint",
+    srcs: ["src/main/java/**/*.kt"],
+    libs: ["lint_api"],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
new file mode 100644
index 0000000..720f835
--- /dev/null
+++ b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+
+fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
+    val method = callExpression.resolve()?.getUMethod() ?: return false
+    return hasPermissionMethodAnnotation(method)
+}
+
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+        .any {
+            it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
+        }
+
+fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
+    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
new file mode 100644
index 0000000..5f6c6f7
--- /dev/null
+++ b/tools/lint/fix/Android.bp
@@ -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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+python_binary_host {
+    name: "lint_fix",
+    main: "lint_fix.py",
+    srcs: ["lint_fix.py"],
+}
diff --git a/tools/lint/Android.bp b/tools/lint/framework/Android.bp
similarity index 87%
rename from tools/lint/Android.bp
rename to tools/lint/framework/Android.bp
index 96618f4..7f27e8a 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/framework/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 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.
@@ -29,6 +29,11 @@
         "auto_service_annotations",
         "lint_api",
     ],
+    static_libs: [
+        "AndroidCommonLint",
+        // TODO: remove once b/236558918 is resolved and the below checks actually run globally
+        "AndroidGlobalLintChecker",
+    ],
     kotlincflags: ["-Xjvm-default=all"],
 }
 
@@ -51,9 +56,3 @@
         unit_test: true,
     },
 }
-
-python_binary_host {
-    name: "lint_fix",
-    main: "fix/lint_fix.py",
-    srcs: ["fix/lint_fix.py"],
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
similarity index 93%
rename from tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 741655b..413e197 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -21,7 +21,7 @@
 import com.android.tools.lint.detector.api.CURRENT_API
 import com.google.android.lint.aidl.EnforcePermissionDetector
 import com.google.android.lint.aidl.EnforcePermissionHelperDetector
-import com.google.android.lint.aidl.ManualPermissionCheckDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
 import com.google.android.lint.parcel.SaferParcelChecker
 import com.google.auto.service.AutoService
 
@@ -40,7 +40,7 @@
         EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
         EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
         EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
-        ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+        SimpleManualPermissionEnforcementDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
         RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
similarity index 97%
rename from tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
index 1b0f035..e12ec3d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
@@ -26,7 +26,6 @@
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.android.tools.lint.detector.api.getUMethod
-import com.google.android.lint.aidl.hasPermissionMethodAnnotation
 import com.intellij.psi.PsiType
 import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UBlockExpression
@@ -193,5 +192,8 @@
                 else -> false
             }
         }
+
+        private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+                .any { it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) }
     }
 }
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
diff --git a/tools/lint/Android.bp b/tools/lint/global/Android.bp
similarity index 84%
copy from tools/lint/Android.bp
copy to tools/lint/global/Android.bp
index 96618f4..3756abe 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/global/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 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.
@@ -22,18 +22,22 @@
 }
 
 java_library_host {
-    name: "AndroidFrameworkLintChecker",
+    name: "AndroidGlobalLintChecker",
     srcs: ["checks/src/main/java/**/*.kt"],
     plugins: ["auto_service_plugin"],
     libs: [
         "auto_service_annotations",
         "lint_api",
     ],
+    static_libs: ["AndroidCommonLint"],
     kotlincflags: ["-Xjvm-default=all"],
+    dist: {
+        targets: ["droid"],
+    },
 }
 
 java_test_host {
-    name: "AndroidFrameworkLintCheckerTest",
+    name: "AndroidGlobalLintCheckerTest",
     // TODO(b/239881504): Since this test was written, Android
     // Lint was updated, and now includes classes that were
     // compiled for java 15. The soong build doesn't support
@@ -42,7 +46,7 @@
     enabled: false,
     srcs: ["checks/src/test/java/**/*.kt"],
     static_libs: [
-        "AndroidFrameworkLintChecker",
+        "AndroidGlobalLintChecker",
         "junit",
         "lint",
         "lint_tests",
@@ -51,9 +55,3 @@
         unit_test: true,
     },
 }
-
-python_binary_host {
-    name: "lint_fix",
-    main: "fix/lint_fix.py",
-    srcs: ["fix/lint_fix.py"],
-}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
new file mode 100644
index 0000000..b377d50
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidGlobalIssueRegistry : IssueRegistry() {
+    override val issues = listOf(
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            SimpleManualPermissionEnforcementDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+    )
+
+    override val api: Int
+        get() = CURRENT_API
+
+    override val minApi: Int
+        get() = 8
+
+    override val vendor: Vendor = Vendor(
+            vendorName = "Android",
+            feedbackUrl = "http://b/issues/new?component=315013",
+            contact = "repsonsible-apis@google.com"
+    )
+}
\ No newline at end of file
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
new file mode 100644
index 0000000..227cdcd
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Abstract class for detectors that look for methods implementing
+ * generated AIDL interface stubs
+ */
+abstract class AidlImplementationDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            val interfaceName = getContainingAidlInterface(node)
+                    .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+            val body = (node.uastBody as? UBlockExpression) ?: return
+            visitAidlMethod(context, node, interfaceName, body)
+        }
+    }
+
+    abstract fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression,
+    )
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
similarity index 97%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index d120e1d..f1b6348 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -19,6 +19,8 @@
 import com.android.tools.lint.detector.api.JavaContext
 import com.android.tools.lint.detector.api.Location
 import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.hasPermissionNameAnnotation
+import com.google.android.lint.isPermissionMethodCall
 import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.evaluateString
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
similarity index 69%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
index edbdd8d..250ca78 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -16,14 +16,9 @@
 
 package com.google.android.lint.aidl
 
-import com.android.tools.lint.detector.api.getUMethod
-import com.google.android.lint.ANNOTATION_PERMISSION_METHOD
-import com.google.android.lint.ANNOTATION_PERMISSION_NAME
 import com.google.android.lint.CLASS_STUB
 import com.intellij.psi.PsiAnonymousClass
-import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UParameter
 
 /**
  * Given a UMethod, determine if this method is
@@ -51,17 +46,3 @@
         it.referenceName == CLASS_STUB
     } ?: false
 }
-
-fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
-    val method = callExpression.resolve()?.getUMethod() ?: return false
-    return hasPermissionMethodAnnotation(method)
-}
-
-fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
-    .any {
-        it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
-    }
-
-fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
-    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
-}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
new file mode 100644
index 0000000..4c0cbe7
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ *
+ * TODO: b/242564870 (enable parse and autoFix of .aidl files)
+ */
+@Suppress("UnstableApiUsage")
+class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() {
+    override fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression
+    ) {
+        val fix = accumulateSimplePermissionCheckFixes(body, context) ?: return
+
+        val javaRemoveFixes = fix.locations.map {
+            fix()
+                    .replace()
+                    .reformat(true)
+                    .range(it)
+                    .with("")
+                    .autoFix()
+                    .build()
+        }
+
+        val javaAnnotateFix = fix()
+                .annotate(fix.annotation)
+                .range(context.getLocation(node))
+                .autoFix()
+                .build()
+
+        context.report(
+                ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+                fix.locations.last(),
+                "$interfaceName permission check can be converted to @EnforcePermission annotation",
+                fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
+        )
+    }
+
+    /**
+     * Walk the expressions in the method, looking for simple permission checks.
+     *
+     * If a single permission check is found at the beginning of the method,
+     * this should be migrated to @EnforcePermission(value).
+     *
+     * If multiple consecutive permission checks are found,
+     * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
+     *
+     * As soon as something other than a permission check is encountered, stop looking,
+     * as some other business logic is happening that prevents an automated fix.
+     */
+    private fun accumulateSimplePermissionCheckFixes(
+            methodBody: UBlockExpression,
+            context: JavaContext
+    ):
+            EnforcePermissionFix? {
+        val singleFixes = mutableListOf<EnforcePermissionFix>()
+        for (expression in methodBody.expressions) {
+            singleFixes.add(getPermissionCheckFix(expression, context) ?: break)
+        }
+        return when (singleFixes.size) {
+            0 -> null
+            1 -> singleFixes[0]
+            else -> EnforcePermissionFix.compose(singleFixes)
+        }
+    }
+
+    /**
+     * If an expression boils down to a permission check, return
+     * the helper for creating a lint auto fix to @EnforcePermission
+     */
+    private fun getPermissionCheckFix(startingExpression: UElement?, context: JavaContext):
+            EnforcePermissionFix? {
+        return when (startingExpression) {
+            is UQualifiedReferenceExpression -> getPermissionCheckFix(
+                    startingExpression.selector, context
+            )
+
+            is UIfExpression -> getPermissionCheckFix(startingExpression.condition, context)
+
+            is UCallExpression -> return EnforcePermissionFix
+                    .fromCallExpression(context, startingExpression)
+
+            else -> null
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION = """
+            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+            annotation to declare the permissions to be enforced.  The verification code is then
+            generated by the AIDL compiler, which also takes care of annotating the generated java
+            code.
+
+            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+            It also enables easier auditing and review.
+
+            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
+                id = "SimpleManualPermissionEnforcement",
+                briefDescription = "Manual permission check can be @EnforcePermission annotation",
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 5,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        SimpleManualPermissionEnforcementDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                ),
+                enabledByDefault = false, // TODO: enable once b/241171714 is resolved
+        )
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
similarity index 93%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
index d4a3497..150fc26 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -23,10 +23,10 @@
 import com.android.tools.lint.detector.api.Issue
 
 @Suppress("UnstableApiUsage")
-class ManualPermissionCheckDetectorTest : LintDetectorTest() {
-    override fun getDetector(): Detector = ManualPermissionCheckDetector()
+class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = SimpleManualPermissionEnforcementDetector()
     override fun getIssues(): List<Issue> = listOf(
-        ManualPermissionCheckDetector
+            SimpleManualPermissionEnforcementDetector
             .ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION
     )
 
@@ -52,7 +52,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -92,7 +92,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                             mContext.enforceCallingOrSelfPermission(
                             ^
                 0 errors, 1 warnings
@@ -132,7 +132,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -174,7 +174,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                             mContext.enforceCallingOrSelfPermission(
                             ^
                 0 errors, 1 warnings
@@ -243,7 +243,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         helper();
                         ~~~~~~~~~
                 0 errors, 1 warnings
@@ -289,7 +289,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         mContext.enforceCallingOrSelfPermission("FOO", "foo");
                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -340,7 +340,7 @@
             .run()
             .expect(
                 """
-                src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         helperHelper();
                         ~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index f29d9b2..c6f6d45 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -54,6 +54,7 @@
             "java.lang.Short",
             "java.lang.String",
             "java.lang.Void",
+            "java.util.UUID",
             "android.os.Parcelable.Creator",
         )