Merge "Record resource access times"
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 72553ed..90ce8bf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -366,7 +366,7 @@
                     job.getNumFailures());
             // Use the context's ID to distinguish traces since there'll only be one job running
             // per context.
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getBatteryName(), getId());
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getTag(), getId());
             try {
                 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
             } catch (RemoteException e) {
@@ -1030,7 +1030,7 @@
                 completedJob.getJob().getPriority(),
                 completedJob.getEffectivePriority(),
                 completedJob.getNumFailures());
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getBatteryName(), getId());
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getTag(), getId());
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
                     internalStopReason);
diff --git a/api/Android.bp b/api/Android.bp
index e9930e3..29eaec6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -38,23 +38,9 @@
     pluginFor: ["soong_build"],
 }
 
-python_defaults {
-    name: "python3_version_defaults",
-    version: {
-        py2: {
-            enabled: false,
-        },
-        py3: {
-            enabled: true,
-            embedded_launcher: false,
-        },
-    },
-}
-
 python_binary_host {
     name: "api_versions_trimmer",
     srcs: ["api_versions_trimmer.py"],
-    defaults: ["python3_version_defaults"],
 }
 
 python_test_host {
@@ -64,7 +50,6 @@
         "api_versions_trimmer_unittests.py",
         "api_versions_trimmer.py",
     ],
-    defaults: ["python3_version_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -73,7 +58,6 @@
 python_binary_host {
     name: "merge_annotation_zips",
     srcs: ["merge_annotation_zips.py"],
-    defaults: ["python3_version_defaults"],
 }
 
 python_test_host {
@@ -83,7 +67,6 @@
         "merge_annotation_zips.py",
         "merge_annotation_zips_test.py",
     ],
-    defaults: ["python3_version_defaults"],
     test_options: {
         unit_test: true,
     },
diff --git a/core/api/current.txt b/core/api/current.txt
index 1bf0d0e..19713cf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5933,6 +5933,7 @@
     field public static final String EXTRA_BIG_TEXT = "android.bigText";
     field public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
     field public static final String EXTRA_CALL_PERSON = "android.callPerson";
+    field public static final String EXTRA_CALL_TYPE = "android.callType";
     field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
     field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
     field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
@@ -6240,6 +6241,10 @@
     method @NonNull public android.app.Notification.CallStyle setIsVideo(boolean);
     method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence);
+    field public static final int CALL_TYPE_INCOMING = 1; // 0x1
+    field public static final int CALL_TYPE_ONGOING = 2; // 0x2
+    field public static final int CALL_TYPE_SCREENING = 3; // 0x3
+    field public static final int CALL_TYPE_UNKNOWN = 0; // 0x0
   }
 
   public static final class Notification.CarExtender implements android.app.Notification.Extender {
@@ -18919,8 +18924,8 @@
     method public android.view.View onCreateCandidatesView();
     method public android.view.View onCreateExtractTextView();
     method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest(@NonNull android.os.Bundle);
-    method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
-    method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
+    method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
+    method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public android.view.View onCreateInputView();
     method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
     method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
@@ -41572,7 +41577,7 @@
     field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool";
     field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
     field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
-    field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
+    field @Deprecated public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 449900d..7fd4283 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2804,6 +2804,7 @@
     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 @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);
@@ -4373,6 +4374,11 @@
 
 package android.hardware.input {
 
+  public class VirtualDpad implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
+  }
+
   public final class VirtualKeyEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getAction();
@@ -10375,6 +10381,7 @@
     field public static final String NAMESPACE_LOCATION = "location";
     field public static final String NAMESPACE_MEDIA = "media";
     field public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
+    field public static final String NAMESPACE_NEARBY = "nearby";
     field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
     field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
     field public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
@@ -10404,6 +10411,7 @@
     field public static final String NAMESPACE_TELEPHONY = "telephony";
     field public static final String NAMESPACE_TETHERING = "tethering";
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
+    field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
     field public static final String NAMESPACE_UWB = "uwb";
     field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 085bd55..887af1f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1874,7 +1874,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
-    method public static boolean isUsersOnSecondaryDisplaysEnabled();
+    method public boolean isUsersOnSecondaryDisplaysSupported();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
 
@@ -2209,6 +2209,7 @@
     field public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service";
     field public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
     field public static final String AUTOFILL_SERVICE = "autofill_service";
+    field public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled";
     field public static final String CONTENT_CAPTURE_ENABLED = "content_capture_enabled";
     field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services";
     field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 0a906be..f9b8a30 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -873,6 +873,8 @@
     New setting keys are not allowed (Field: ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); use getters/setters in relevant manager class
 NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_SERVICE:
     New setting keys are not allowed (Field: AUTOFILL_SERVICE); use getters/setters in relevant manager class
+NoSettingsProvider: android.provider.Settings.Secure#BIOMETRIC_VIRTUAL_ENABLED:
+    New setting keys are not allowed (Field: BIOMETRIC_VIRTUAL_ENABLED); use getters/setters in relevant manager class
 NoSettingsProvider: android.provider.Settings.Secure#CONTENT_CAPTURE_ENABLED:
     New setting keys are not allowed (Field: CONTENT_CAPTURE_ENABLED); use getters/setters in relevant manager class
 NoSettingsProvider: android.provider.Settings.Secure#DISABLED_PRINT_SERVICES:
@@ -1011,7 +1013,7 @@
     Classes holding a set of parameters should be called `FooParams`, was `ContentCaptureOptions`
 
 
-VisiblySynchronized: PsiThisExpression:this:
+VisiblySynchronized: PsiThisExpression:
     Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths()
 VisiblySynchronized: android.content.res.AssetManager#getApkPaths():
     Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths()
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c2b315f..212e358 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -89,6 +89,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.Selection;
@@ -8304,13 +8305,15 @@
         mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
         mShouldDockBigOverlays = getResources().getBoolean(R.bool.config_dockBigOverlayWindows);
         restoreHasCurrentPermissionRequest(icicle);
+        final long startTime = SystemClock.uptimeMillis();
         if (persistentState != null) {
             onCreate(icicle, persistentState);
         } else {
             onCreate(icicle);
         }
+        final long duration = SystemClock.uptimeMillis() - startTime;
         EventLogTags.writeWmOnCreateCalled(mIdent, getComponentName().getClassName(),
-                "performCreate");
+                "performCreate", duration);
         mActivityTransitionState.readState(icicle);
 
         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
@@ -8332,8 +8335,11 @@
         mFragments.noteStateNotSaved();
         mCalled = false;
         mFragments.execPendingActions();
+        final long startTime = SystemClock.uptimeMillis();
         mInstrumentation.callActivityOnStart(this);
-        EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason);
+        final long duration = SystemClock.uptimeMillis() - startTime;
+        EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason,
+                duration);
 
         if (!mCalled) {
             throw new SuperNotCalledException(
@@ -8410,8 +8416,11 @@
             }
 
             mCalled = false;
+            final long startTime = SystemClock.uptimeMillis();
             mInstrumentation.callActivityOnRestart(this);
-            EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason);
+            final long duration = SystemClock.uptimeMillis() - startTime;
+            EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason,
+                    duration);
             if (!mCalled) {
                 throw new SuperNotCalledException(
                     "Activity " + mComponent.toShortString() +
@@ -8438,9 +8447,12 @@
         getAutofillClientController().onActivityPerformResume(followedByPause);
 
         mCalled = false;
+        final long startTime = SystemClock.uptimeMillis();
         // mResumed is set by the instrumentation
         mInstrumentation.callActivityOnResume(this);
-        EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason);
+        final long duration = SystemClock.uptimeMillis() - startTime;
+        EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason,
+                duration);
         if (!mCalled) {
             throw new SuperNotCalledException(
                 "Activity " + mComponent.toShortString() +
@@ -8483,9 +8495,11 @@
         mDoReportFullyDrawn = false;
         mFragments.dispatchPause();
         mCalled = false;
+        final long startTime = SystemClock.uptimeMillis();
         onPause();
+        final long duration = SystemClock.uptimeMillis() - startTime;
         EventLogTags.writeWmOnPausedCalled(mIdent, getComponentName().getClassName(),
-                "performPause");
+                "performPause", duration);
         mResumed = false;
         if (!mCalled && getApplicationInfo().targetSdkVersion
                 >= android.os.Build.VERSION_CODES.GINGERBREAD) {
@@ -8529,8 +8543,11 @@
             mFragments.dispatchStop();
 
             mCalled = false;
+            final long startTime = SystemClock.uptimeMillis();
             mInstrumentation.callActivityOnStop(this);
-            EventLogTags.writeWmOnStopCalled(mIdent, getComponentName().getClassName(), reason);
+            final long duration = SystemClock.uptimeMillis() - startTime;
+            EventLogTags.writeWmOnStopCalled(mIdent, getComponentName().getClassName(), reason,
+                    duration);
             if (!mCalled) {
                 throw new SuperNotCalledException(
                     "Activity " + mComponent.toShortString() +
@@ -8564,9 +8581,11 @@
         mDestroyed = true;
         mWindow.destroy();
         mFragments.dispatchDestroy();
+        final long startTime = SystemClock.uptimeMillis();
         onDestroy();
+        final long duration = SystemClock.uptimeMillis() - startTime;
         EventLogTags.writeWmOnDestroyCalled(mIdent, getComponentName().getClassName(),
-                "performDestroy");
+                "performDestroy", duration);
         mFragments.doLoaderDestroy();
         if (mVoiceInteractor != null) {
             mVoiceInteractor.detachActivity();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 28404d5..03c1e07 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1910,6 +1910,7 @@
             OP_SCHEDULE_EXACT_ALARM,
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
+            OP_GET_USAGE_STATS,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
diff --git a/core/java/android/app/EventLogTags.logtags b/core/java/android/app/EventLogTags.logtags
index d0856f4..479f5a6 100644
--- a/core/java/android/app/EventLogTags.logtags
+++ b/core/java/android/app/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package android.app
 
@@ -6,21 +6,21 @@
 # google3/googledata/wireless/android/provisioning/gservices.config !!
 #
 # The activity's onPause has been called.
-30021 wm_on_paused_called (Token|1|5),(Component Name|3),(Reason|3)
+30021 wm_on_paused_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 # The activity's onResume has been called.
-30022 wm_on_resume_called (Token|1|5),(Component Name|3),(Reason|3)
+30022 wm_on_resume_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 
 # The activity's onStop has been called.
-30049 wm_on_stop_called (Token|1|5),(Component Name|3),(Reason|3)
+30049 wm_on_stop_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 
 # The activity's onCreate has been called.
-30057 wm_on_create_called (Token|1|5),(Component Name|3),(Reason|3)
+30057 wm_on_create_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 # The activity's onRestart has been called.
-30058 wm_on_restart_called (Token|1|5),(Component Name|3),(Reason|3)
+30058 wm_on_restart_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 # The activity's onStart has been called.
-30059 wm_on_start_called (Token|1|5),(Component Name|3),(Reason|3)
+30059 wm_on_start_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 # The activity's onDestroy has been called.
-30060 wm_on_destroy_called (Token|1|5),(Component Name|3),(Reason|3)
+30060 wm_on_destroy_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3)
 # The activity's onActivityResult has been called.
 30062 wm_on_activity_result_called (Token|1|5),(Component Name|3),(Reason|3)
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 343adb8..9bbebc8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1421,7 +1421,6 @@
     /**
      * {@link #extras} key: the type of call represented by the
      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
-     * @hide
      */
     public static final String EXTRA_CALL_TYPE = "android.callType";
 
@@ -9301,11 +9300,43 @@
      * </pre>
      */
     public static class CallStyle extends Style {
-        /** @hide */
+
+        /**
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                CALL_TYPE_UNKNOWN,
+                CALL_TYPE_INCOMING,
+                CALL_TYPE_ONGOING,
+                CALL_TYPE_SCREENING
+        })
+        public @interface CallType {};
+
+        /**
+         * Unknown call type.
+         *
+         * See {@link #EXTRA_CALL_TYPE}.
+         */
+        public static final int CALL_TYPE_UNKNOWN = 0;
+
+        /**
+         *  Call type for incoming calls.
+         *
+         *  See {@link #EXTRA_CALL_TYPE}.
+         */
         public static final int CALL_TYPE_INCOMING = 1;
-        /** @hide */
+        /**
+         * Call type for ongoing calls.
+         *
+         * See {@link #EXTRA_CALL_TYPE}.
+         */
         public static final int CALL_TYPE_ONGOING = 2;
-        /** @hide */
+        /**
+         * Call type for calls that are being screened.
+         *
+         * See {@link #EXTRA_CALL_TYPE}.
+         */
         public static final int CALL_TYPE_SCREENING = 3;
 
         /**
@@ -9392,13 +9423,14 @@
         }
 
         /**
+         * @param callType The type of the call
          * @param person The person displayed for the incoming call.
          *             The user also needs to have a non-empty name associated with it.
          * @param hangUpIntent The intent to be sent when the user taps the hang up action
          * @param declineIntent The intent to be sent when the user taps the decline action
          * @param answerIntent The intent to be sent when the user taps the answer action
          */
-        private CallStyle(int callType, @NonNull Person person,
+        private CallStyle(@CallType int callType, @NonNull Person person,
                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
                 @Nullable PendingIntent answerIntent) {
             if (person == null || TextUtils.isEmpty(person.getName())) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 8cfbf2f..9c99da5 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -57,6 +57,12 @@
 
     void onAudioSessionEnded();
 
+    void createVirtualDpad(
+            int displayId,
+            String inputDeviceName,
+            int vendorId,
+            int productId,
+            IBinder token);
     void createVirtualKeyboard(
             int displayId,
             String inputDeviceName,
@@ -77,6 +83,7 @@
             IBinder token,
             in Point screenSize);
     void unregisterInputDevice(IBinder token);
+    boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
     boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
     boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
     boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 1b93bb8..d4c9a42 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -37,6 +37,7 @@
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplay;
 import android.hardware.display.VirtualDisplayConfig;
+import android.hardware.input.VirtualDpad;
 import android.hardware.input.VirtualKeyboard;
 import android.hardware.input.VirtualMouse;
 import android.hardware.input.VirtualTouchscreen;
@@ -315,6 +316,32 @@
         }
 
         /**
+         * Creates a virtual dpad.
+         *
+         * @param display the display that the events inputted through this device should target
+         * @param inputDeviceName the name to call this input device
+         * @param vendorId the PCI vendor id
+         * @param productId the product id, as defined by the vendor
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualDpad createVirtualDpad(
+                @NonNull VirtualDisplay display,
+                @NonNull String inputDeviceName,
+                int vendorId,
+                int productId) {
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.input.VirtualDpad:" + inputDeviceName);
+                mVirtualDevice.createVirtualDpad(display.getDisplay().getDisplayId(),
+                        inputDeviceName, vendorId, productId, token);
+                return new VirtualDpad(mVirtualDevice, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Creates a virtual keyboard.
          *
          * @param display the display that the events inputted through this device should target
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index c59d757..257ad71 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -315,17 +315,17 @@
     int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
 
     /**
-     * Whether the FingerprintAcquired message is a signal to turn off HBM
+     * Whether the FingerprintAcquired message is a signal to disable the UDFPS display mode.
+     * We want to disable the UDFPS mode as soon as possible to conserve power and provide better
+     * UX. For example, prolonged high-brightness illumination of optical sensors can be unpleasant
+     * to the user, can cause long term display burn-in, and can drain the battery faster.
      */
-    static boolean shouldTurnOffHbm(@FingerprintAcquired int acquiredInfo) {
+    static boolean shouldDisableUdfpsDisplayMode(@FingerprintAcquired int acquiredInfo) {
         switch (acquiredInfo) {
             case FINGERPRINT_ACQUIRED_START:
-                // Authentication just began
+                // Keep the UDFPS mode because the authentication just began.
                 return false;
             case FINGERPRINT_ACQUIRED_GOOD:
-                // Good image captured. Turn off HBM. Success/Reject comes after, which is when
-                // hideUdfpsOverlay will be called.
-                return true;
             case FINGERPRINT_ACQUIRED_PARTIAL:
             case FINGERPRINT_ACQUIRED_INSUFFICIENT:
             case FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
@@ -334,11 +334,12 @@
             case FINGERPRINT_ACQUIRED_IMMOBILE:
             case FINGERPRINT_ACQUIRED_TOO_BRIGHT:
             case FINGERPRINT_ACQUIRED_VENDOR:
-                // Bad image captured. Turn off HBM. Matcher will not run, so there's no need to
-                // keep HBM on.
+                // Disable the UDFPS mode because the image capture has finished. The overlay
+                // can be hidden later, once the authentication result arrives.
                 return true;
             case FINGERPRINT_ACQUIRED_UNKNOWN:
             default:
+                // Keep the UDFPS mode in case of an unknown message.
                 return false;
         }
     }
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
new file mode 100644
index 0000000..d7cda9e
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+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 android.view.KeyEvent;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A virtual dpad representing a key input mechanism on a remote device.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualDpad implements Closeable {
+
+    private final Set<Integer> mSupportedKeyCodes =
+            Collections.unmodifiableSet(
+                    new HashSet<>(
+                            Arrays.asList(
+                                    KeyEvent.KEYCODE_DPAD_UP,
+                                    KeyEvent.KEYCODE_DPAD_DOWN,
+                                    KeyEvent.KEYCODE_DPAD_LEFT,
+                                    KeyEvent.KEYCODE_DPAD_RIGHT,
+                                    KeyEvent.KEYCODE_DPAD_CENTER)));
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /** @hide */
+    public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) {
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void close() {
+        try {
+            mVirtualDevice.unregisterInputDevice(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends a key event to the system.
+     *
+     * Supported key codes are KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT,
+     * KEYCODE_DPAD_RIGHT and KEYCODE_DPAD_CENTER,
+     *
+     * @param event the event to send
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendKeyEvent(@NonNull VirtualKeyEvent event) {
+        try {
+            if (!mSupportedKeyCodes.contains(event.getKeyCode())) {
+                throw new IllegalArgumentException(
+                        "Unsupported key code "
+                                + event.getKeyCode()
+                                + " sent to a VirtualDpad input device.");
+            }
+            mVirtualDevice.sendDpadKeyEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index 80a49c9..dc30e55 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -158,6 +158,7 @@
             KeyEvent.KEYCODE_DPAD_UP,
             KeyEvent.KEYCODE_DPAD_LEFT,
             KeyEvent.KEYCODE_DPAD_RIGHT,
+            KeyEvent.KEYCODE_DPAD_CENTER,
             KeyEvent.KEYCODE_MOVE_END,
             KeyEvent.KEYCODE_MOVE_HOME,
             KeyEvent.KEYCODE_PAGE_DOWN,
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index ee9b659..901401fe 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -22,6 +22,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.KeyEvent;
 
 import java.io.Closeable;
 
@@ -37,6 +38,7 @@
 @SystemApi
 public class VirtualKeyboard implements Closeable {
 
+    private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER;
     private final IVirtualDevice mVirtualDevice;
     private final IBinder mToken;
 
@@ -64,6 +66,11 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendKeyEvent(@NonNull VirtualKeyEvent event) {
         try {
+            if (mUnsupportedKeyCode == event.getKeyCode()) {
+                throw new IllegalArgumentException(
+                    "Unsupported key code " + event.getKeyCode()
+                        + " sent to a VirtualKeyboard input device.");
+            }
             mVirtualDevice.sendKeyEvent(mToken, event);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 3a157d3..7436601 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1744,7 +1744,16 @@
     /**
      * Implement to return our standard {@link InputMethodImpl}.  Subclasses
      * can override to provide their own customized version.
+     *
+     * @deprecated IME developers don't need to override this method to get callbacks information.
+     * Most methods in {@link InputMethodImpl} have corresponding callbacks.
+     * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()},
+     * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc.
+     *
+     * <p>Starting from Android U and later, override this method won't guarantee that IME works
+     * as previous platform behavior.</p>
      */
+    @Deprecated
     @Override
     public AbstractInputMethodImpl onCreateInputMethodInterface() {
         return new InputMethodImpl();
@@ -1753,7 +1762,15 @@
     /**
      * Implement to return our standard {@link InputMethodSessionImpl}.  Subclasses
      * can override to provide their own customized version.
+     *
+     * @deprecated IME developers don't need to override this method to get callbacks information.
+     * Most methods in {@link InputMethodSessionImpl} have corresponding callbacks.
+     * Use {@link InputMethodService#onFinishInput()},
+     * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
+     * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
+     * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
      */
+    @Deprecated
     @Override
     public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
         return new InputMethodSessionImpl();
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 3979c6c..10ce3bf 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -483,9 +483,6 @@
         final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */,
                 mIsRestrictedToTestNetworks, mExcludeLocalRoutes, mRequiresInternetValidation,
                 mIkeTunConnParams);
-
-        profile.server = getServerAddr();
-        profile.ipsecIdentifier = getUserIdentity();
         profile.proxy = mProxyInfo;
         profile.isBypassable = mIsBypassable;
         profile.isMetered = mIsMetered;
@@ -499,6 +496,8 @@
         }
 
         profile.type = mType;
+        profile.server = getServerAddr();
+        profile.ipsecIdentifier = getUserIdentity();
         profile.setAllowedAlgorithms(mAllowedAlgorithms);
         switch (mType) {
             case TYPE_IKEV2_IPSEC_USER_PASS:
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 113a640..a6b62b0 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -441,7 +441,7 @@
      * different penalties for different detected actions.
      */
     public static final class ThreadPolicy {
-        /** The default, lax policy which doesn't catch anything. */
+        /** The lax policy which doesn't catch anything. */
         public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
 
         @UnsupportedAppUsage
@@ -728,7 +728,7 @@
      * <p>The policy is enabled by {@link #setVmPolicy}.
      */
     public static final class VmPolicy {
-        /** The default, lax policy which doesn't catch anything. */
+        /** The lax policy which doesn't catch anything. */
         public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
 
         @UnsupportedAppUsage
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index ecea054..2b75a23 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -377,7 +377,7 @@
                 }
                 long currentNanos = elapsedRealtimeNanos();
                 long deltaMs = (currentNanos - time.getElapsedRealtimeNanos()) / 1000000L;
-                return time.getTime() + deltaMs;
+                return time.getUnixEpochTimeMillis() + deltaMs;
             }
         };
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f6aaee8..1e91700 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2843,7 +2843,6 @@
     /**
      * @hide
      */
-    @TestApi
     public static boolean isUsersOnSecondaryDisplaysEnabled() {
         return SystemProperties.getBoolean("fw.users_on_secondary_displays",
                 Resources.getSystem()
@@ -2853,10 +2852,12 @@
     /**
      * Returns whether the device allows users to run (and launch activities) on secondary displays.
      *
-     * @return {@code false} for most devices, except automotive vehicles with passenger displays.
+     * @return {@code false} for most devices, except on automotive builds for vehiches with
+     * passenger displays.
      *
      * @hide
      */
+    @TestApi
     public boolean isUsersOnSecondaryDisplaysSupported() {
         return isUsersOnSecondaryDisplaysEnabled();
     }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index fe46c9e..345486e 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -203,6 +203,15 @@
     @SystemApi
     public static final String NAMESPACE_TETHERING = "tethering";
 
+
+    /**
+     * Namespace for Nearby module.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_NEARBY = "nearby";
+
     /**
      * Namespace for content capture feature used by on-device machine intelligence
      * to provide suggestions in a privacy-safe manner.
@@ -776,6 +785,14 @@
     public static final String NAMESPACE_WEAR = "wear";
 
     /**
+     * Namespace for features relating to MBA transparency metadata.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
+
+    /**
      * Namespace for the input method manager platform features.
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a99d0f0..3a9af7e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9825,6 +9825,7 @@
          * Whether or not virtual sensors are enabled.
          * @hide
          */
+        @TestApi
         @Readable
         public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled";
 
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index f900558..8efc5eb 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -68,6 +68,8 @@
 
     public static final int KM_TAG_RSA_PUBLIC_EXPONENT = Tag.RSA_PUBLIC_EXPONENT; // KM_ULONG | 200;
     public static final int KM_TAG_INCLUDE_UNIQUE_ID = Tag.INCLUDE_UNIQUE_ID; // KM_BOOL | 202;
+    public static final int KM_TAG_RSA_OAEP_MGF_DIGEST = Tag.RSA_OAEP_MGF_DIGEST;
+            // KM_ENUM_REP | 203;
 
     public static final int KM_TAG_ACTIVE_DATETIME = Tag.ACTIVE_DATETIME; // KM_DATE | 400;
     public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME =
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 537dffc..d48d566 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -170,7 +170,7 @@
      * mean using 12-hour in some locales and, in this case, is duplicated as the 'a' field.
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long DISALLOW_DUPLICATE_FIELD_IN_SKELETON = 170233598L;
 
     /**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 20f01ae..cb8f0af 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -96,13 +96,6 @@
     /** @hide */
     public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
 
-
-    /** Support Clear Calling feature.
-     *  @hide
-     */
-    public static final String SETTINGS_ENABLE_CLEAR_CALLING = "settings_enable_clear_calling";
-
-
     /** Flag to enable / disable the Simple Cursor accessibility feature in
      *  Settings.
      * @hide
@@ -122,6 +115,11 @@
      */
     public static final String SETTINGS_ENABLE_SPA = "settings_enable_spa";
 
+    /** Flag to enable/disable adb log metrics
+     *  @hide
+     */
+    public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -151,10 +149,10 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
-        DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
         DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b2a26fa..6b9fceb7 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -67,6 +67,7 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.ITaskFpsCallback;
+import android.window.ScreenCapture;
 
 /**
  * System private interface to the window manager.
@@ -968,4 +969,7 @@
      * treatment.
      */
     boolean isLetterboxBackgroundMultiColored();
+
+    oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
+            in ScreenCapture.ScreenCaptureListener listener);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 15aa4b4..894ce63 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24515,9 +24515,8 @@
     /**
      * Set the current default focus highlight.
      * @param highlight the highlight drawable, or {@code null} if it's no longer needed.
-     * @hide
      */
-    void setDefaultFocusHighlight(Drawable highlight) {
+    private void setDefaultFocusHighlight(Drawable highlight) {
         mDefaultFocusHighlight = highlight;
         mDefaultFocusHighlightSizeChanged = true;
         if (highlight != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4b16788..b6f775d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3098,7 +3098,8 @@
                 // WindowManagerService has reported back a frame from a configuration not yet
                 // handled by the client. In this case, we need to accept the configuration so we
                 // do not lay out and draw with the wrong configuration.
-                if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
+                if (mRelayoutRequested
+                        && !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
                     if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
                             + mPendingMergedConfiguration.getMergedConfiguration());
                     performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
@@ -3814,13 +3815,6 @@
                 if (mAttachInfo.mTooltipHost != null) {
                     mAttachInfo.mTooltipHost.hideTooltip();
                 }
-                if (!hasWindowFocus) {
-                    // Clear focus highlight if its window lost focus.
-                    final View focused = mView.findFocus();
-                    if (focused != null) {
-                        focused.setDefaultFocusHighlight(null);
-                    }
-                }
             }
 
             // Note: must be done after the focus change callbacks,
@@ -5852,13 +5846,7 @@
             // be when the window is first being added, and mFocused isn't
             // set yet.
             final View focused = mView.findFocus();
-            if (focused == null) {
-                return false;
-            }
-
-            // Clear default focus highlight if it entered touch mode.
-            focused.setDefaultFocusHighlight(null);
-            if (!focused.isFocusableInTouchMode()) {
+            if (focused != null && !focused.isFocusableInTouchMode()) {
                 final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused);
                 if (ancestorToTakeFocus != null) {
                     // there is an ancestor that wants focus after its
@@ -8158,7 +8146,8 @@
         final int measuredWidth = mView.getMeasuredWidth();
         final int measuredHeight = mView.getMeasuredHeight();
         final boolean relayoutAsync;
-        if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility
+        if (LOCAL_LAYOUT
+                && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
                 && mWindowAttributes.type != TYPE_APPLICATION_STARTING
                 && mSyncSeqId <= mLastSyncSeqId
                 && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c7c163..c966e93 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -788,7 +788,7 @@
                 // we'll just do a window focus gain and call it a day.
                 View servedView = controller.getServedView();
                 boolean nextFocusHasConnection = servedView != null && servedView == focusedView
-                        && hasActiveConnection(focusedView);
+                        && hasActiveInputConnectionInternal(focusedView);
                 if (DEBUG) {
                     Log.v(TAG, "Reporting focus gain, without startInput"
                             + ", nextFocusIsServedView=" + nextFocusHasConnection);
@@ -861,22 +861,11 @@
         /**
          * Checks whether the active input connection (if any) is for the given view.
          *
-         * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic.
-         *
-         * Note that this method is only intended for restarting input after focus gain
-         * (e.g. b/160391516), DO NOT leverage this method to do another check.
+         * @see #hasActiveInputConnectionInternal(View)}
          */
         @Override
         public boolean hasActiveConnection(View view) {
-            synchronized (mH) {
-                if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) {
-                    return false;
-                }
-
-                return mServedInputConnection != null
-                        && mServedInputConnection.isActive()
-                        && mServedInputConnection.getServedView() == view;
-            }
+            return hasActiveInputConnectionInternal(view);
         }
     }
 
@@ -889,11 +878,31 @@
      * Checks whether the active input connection (if any) is for the given view.
      *
      * @hide
-     * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View)
+     * @see #hasActiveInputConnectionInternal(View)}
      */
     @TestApi
     public boolean hasActiveInputConnection(@Nullable View view) {
-        return mDelegate.hasActiveConnection(view);
+        return hasActiveInputConnectionInternal(view);
+    }
+
+    /**
+     * Checks whether the active input connection (if any) is for the given view.
+     *
+     * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic.
+     *
+     * Note that this method is only intended for restarting input after focus gain
+     * (e.g. b/160391516), DO NOT leverage this method to do another check.
+     */
+    private boolean hasActiveInputConnectionInternal(@Nullable View view) {
+        synchronized (mH) {
+            if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) {
+                return false;
+            }
+
+            return mServedInputConnection != null
+                    && mServedInputConnection.isActive()
+                    && mServedInputConnection.getServedView() == view;
+        }
     }
 
     @GuardedBy("mH")
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a2a198a..424b8ae 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2875,6 +2875,17 @@
         }
     }
 
+    /**
+     *
+     * @return whether the Blink runnable is blinking or not, if null return false.
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean isBlinking() {
+        if (mBlink == null) return false;
+        return !mBlink.mCancelled;
+    }
+
     private class Blink implements Runnable {
         private boolean mCancelled;
 
diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/ScreenCapture.aidl
new file mode 100644
index 0000000..267a7c6
--- /dev/null
+++ b/core/java/android/window/ScreenCapture.aidl
@@ -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.
+ */
+
+package android.window;
+
+/** @hide */
+parcelable ScreenCapture.CaptureArgs;
+
+/** @hide */
+parcelable ScreenCapture.ScreenshotHardwareBuffer;
+
+/** @hide */
+parcelable ScreenCapture.ScreenCaptureListener;
\ No newline at end of file
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 887d027..c010f22 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -24,11 +24,16 @@
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 import android.view.SurfaceControl;
 
+import libcore.util.NativeAllocationRegistry;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Handles display and layer captures for the system.
@@ -39,18 +44,23 @@
     private static final String TAG = "ScreenCapture";
 
     private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
-            ScreenCaptureListener captureListener);
+            long captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
-            ScreenCaptureListener captureListener);
+            long captureListener);
+    private static native long nativeCreateScreenCaptureListener(
+            Consumer<ScreenshotHardwareBuffer> consumer);
+    private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
+    private static native long nativeReadListenerFromParcel(Parcel in);
+    private static native long getNativeListenerFinalizer();
 
     /**
-     * @param captureArgs Arguments about how to take the screenshot
+     * @param captureArgs     Arguments about how to take the screenshot
      * @param captureListener A listener to receive the screenshot callback
      * @hide
      */
     public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureDisplay(captureArgs, captureListener);
+        return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject);
     }
 
     /**
@@ -61,10 +71,8 @@
      */
     public static ScreenshotHardwareBuffer captureDisplay(
             DisplayCaptureArgs captureArgs) {
-        SyncScreenCaptureListener
-                screenCaptureListener = new SyncScreenCaptureListener();
-
-        int status = captureDisplay(captureArgs, screenCaptureListener);
+        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
+        int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener());
         if (status != 0) {
             return null;
         }
@@ -75,14 +83,13 @@
     /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
-     * @param layer            The root layer to capture.
-     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
-     *                         Rect()' or null if no cropping is desired. If the root layer does not
-     *                         have a buffer or a crop set, then a non-empty source crop must be
-     *                         specified.
-     * @param frameScale       The desired scale of the returned buffer; the raw
-     *                         screen will be scaled up/down.
-     *
+     * @param layer      The root layer to capture.
+     * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+     *                   Rect()' or null if no cropping is desired. If the root layer does not
+     *                   have a buffer or a crop set, then a non-empty source crop must be
+     *                   specified.
+     * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
+     *                   up/down.
      * @return Returns a HardwareBuffer that contains the layer capture.
      * @hide
      */
@@ -94,15 +101,14 @@
     /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
-     * @param layer            The root layer to capture.
-     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
-     *                         Rect()' or null if no cropping is desired. If the root layer does not
-     *                         have a buffer or a crop set, then a non-empty source crop must be
-     *                         specified.
-     * @param frameScale       The desired scale of the returned buffer; the raw
-     *                         screen will be scaled up/down.
-     * @param format           The desired pixel format of the returned buffer.
-     *
+     * @param layer      The root layer to capture.
+     * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+     *                   Rect()' or null if no cropping is desired. If the root layer does not
+     *                   have a buffer or a crop set, then a non-empty source crop must be
+     *                   specified.
+     * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
+     *                   up/down.
+     * @param format     The desired pixel format of the returned buffer.
      * @return Returns a HardwareBuffer that contains the layer capture.
      * @hide
      */
@@ -124,7 +130,7 @@
             LayerCaptureArgs captureArgs) {
         SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
 
-        int status = captureLayers(captureArgs, screenCaptureListener);
+        int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener());
         if (status != 0) {
             return null;
         }
@@ -135,6 +141,7 @@
     /**
      * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
      * handles to exclude.
+     *
      * @hide
      */
     public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
@@ -150,24 +157,13 @@
     }
 
     /**
-     * @param captureArgs Arguments about how to take the screenshot
+     * @param captureArgs     Arguments about how to take the screenshot
      * @param captureListener A listener to receive the screenshot callback
      * @hide
      */
     public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureLayers(captureArgs, captureListener);
-    }
-
-    /**
-     * @hide
-     */
-    public interface ScreenCaptureListener {
-        /**
-         * The callback invoked when the screen capture is complete.
-         * @param hardwareBuffer Data containing info about the screen capture.
-         */
-        void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
+        return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
     }
 
     /**
@@ -190,15 +186,16 @@
             mContainsHdrLayers = containsHdrLayers;
         }
 
-       /**
-        * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
-        * @param hardwareBuffer The existing HardwareBuffer object
-        * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
-        * @param containsSecureLayers Indicates whether this graphic buffer contains captured
-        *                             contents of secure layers, in which case the screenshot
-        *                             should not be persisted.
-        * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
-        */
+        /**
+         * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
+         *
+         * @param hardwareBuffer       The existing HardwareBuffer object
+         * @param namedColorSpace      Integer value of a named color space {@link ColorSpace.Named}
+         * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+         *                             contents of secure layers, in which case the screenshot
+         *                             should not be persisted.
+         * @param containsHdrLayers    Indicates whether this graphic buffer contains HDR content.
+         */
         private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
                 int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
             ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
@@ -220,6 +217,7 @@
         public boolean containsSecureLayers() {
             return mContainsSecureLayers;
         }
+
         /**
          * Returns whether the screenshot contains at least one HDR layer.
          * This information may be useful for informing the display whether this screenshot
@@ -234,7 +232,7 @@
          * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
          * into
          * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
-         *
+         * <p>
          * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
          * directly
          * use the {@link HardwareBuffer} directly.
@@ -250,34 +248,13 @@
         }
     }
 
-    private static class SyncScreenCaptureListener implements ScreenCaptureListener {
-        private static final int SCREENSHOT_WAIT_TIME_S = 1;
-        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
-        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-
-        @Override
-        public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
-            mScreenshotHardwareBuffer = hardwareBuffer;
-            mCountDownLatch.countDown();
-        }
-
-        private ScreenshotHardwareBuffer waitForScreenshot() {
-            try {
-                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to wait for screen capture result", e);
-            }
-
-            return mScreenshotHardwareBuffer;
-        }
-    }
-
     /**
      * A common arguments class used for various screenshot requests. This contains arguments that
      * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
+     *
      * @hide
      */
-    private abstract static class CaptureArgs {
+    public static class CaptureArgs implements Parcelable {
         private final int mPixelFormat;
         private final Rect mSourceCrop = new Rect();
         private final float mFrameScaleX;
@@ -287,7 +264,7 @@
         private final long mUid;
         private final boolean mGrayscale;
 
-        private CaptureArgs(Builder<? extends Builder<?>> builder) {
+        private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
             mSourceCrop.set(builder.mSourceCrop);
             mFrameScaleX = builder.mFrameScaleX;
@@ -298,12 +275,23 @@
             mGrayscale = builder.mGrayscale;
         }
 
+        private CaptureArgs(Parcel in) {
+            mPixelFormat = in.readInt();
+            mSourceCrop.readFromParcel(in);
+            mFrameScaleX = in.readFloat();
+            mFrameScaleY = in.readFloat();
+            mCaptureSecureLayers = in.readBoolean();
+            mAllowProtected = in.readBoolean();
+            mUid = in.readLong();
+            mGrayscale = in.readBoolean();
+        }
+
         /**
          * The Builder class used to construct {@link CaptureArgs}
          *
-         * @param <T> A builder that extends {@link Builder}
+         * @param <T> A builder that extends {@link CaptureArgs.Builder}
          */
-        abstract static class Builder<T extends Builder<T>> {
+        public static class Builder<T extends CaptureArgs.Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
             private float mFrameScaleX = 1;
@@ -314,6 +302,14 @@
             private boolean mGrayscale;
 
             /**
+             * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
+             * valid.
+             */
+            public CaptureArgs build() {
+                return new CaptureArgs(this);
+            }
+
+            /**
              * The desired pixel format of the returned buffer.
              */
             public T setPixelFormat(int pixelFormat) {
@@ -395,15 +391,47 @@
             /**
              * Each sub class should return itself to allow the builder to chain properly
              */
-            abstract T getThis();
+            T getThis() {
+                return (T) this;
+            }
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mPixelFormat);
+            mSourceCrop.writeToParcel(dest, flags);
+            dest.writeFloat(mFrameScaleX);
+            dest.writeFloat(mFrameScaleY);
+            dest.writeBoolean(mCaptureSecureLayers);
+            dest.writeBoolean(mAllowProtected);
+            dest.writeLong(mUid);
+            dest.writeBoolean(mGrayscale);
+        }
+
+        public static final Parcelable.Creator<CaptureArgs> CREATOR =
+                new Parcelable.Creator<CaptureArgs>() {
+                    @Override
+                    public CaptureArgs createFromParcel(Parcel in) {
+                        return new CaptureArgs(in);
+                    }
+
+                    @Override
+                    public CaptureArgs[] newArray(int size) {
+                        return new CaptureArgs[size];
+                    }
+                };
     }
 
     /**
      * The arguments class used to make display capture requests.
      *
-     * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
      * @hide
+     * @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
      */
     public static class DisplayCaptureArgs extends CaptureArgs {
         private final IBinder mDisplayToken;
@@ -488,8 +516,8 @@
     /**
      * The arguments class used to make layer capture requests.
      *
-     * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
      * @hide
+     * @see #nativeCaptureLayers(LayerCaptureArgs, long)
      */
     public static class LayerCaptureArgs extends CaptureArgs {
         private final long mNativeLayer;
@@ -530,6 +558,17 @@
                 return new LayerCaptureArgs(this);
             }
 
+            public Builder(SurfaceControl layer, CaptureArgs args) {
+                setLayer(layer);
+                setPixelFormat(args.mPixelFormat);
+                setSourceCrop(args.mSourceCrop);
+                setFrameScale(args.mFrameScaleX, args.mFrameScaleY);
+                setCaptureSecureLayers(args.mCaptureSecureLayers);
+                setAllowProtected(args.mAllowProtected);
+                setUid(args.mUid);
+                setGrayscale(args.mGrayscale);
+            }
+
             public Builder(SurfaceControl layer) {
                 setLayer(layer);
             }
@@ -542,7 +581,6 @@
                 return this;
             }
 
-
             /**
              * An array of layer handles to exclude.
              */
@@ -564,8 +602,106 @@
             Builder getThis() {
                 return this;
             }
-
         }
     }
 
+    /**
+     * The object used to receive the results when invoking screen capture requests via
+     * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
+     * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
+     */
+    public static class ScreenCaptureListener implements Parcelable {
+        private final long mNativeObject;
+        private static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
+
+        /**
+         * @param consumer The callback invoked when the screen capture is complete.
+         */
+        public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+            mNativeObject = nativeCreateScreenCaptureListener(consumer);
+            sRegistry.registerNativeAllocation(this, mNativeObject);
+        }
+
+        private ScreenCaptureListener(Parcel in) {
+            if (in.readBoolean()) {
+                mNativeObject = nativeReadListenerFromParcel(in);
+                sRegistry.registerNativeAllocation(this, mNativeObject);
+            } else {
+                mNativeObject = 0;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            if (mNativeObject == 0) {
+                dest.writeBoolean(false);
+            } else {
+                dest.writeBoolean(true);
+                nativeWriteListenerToParcel(mNativeObject, dest);
+            }
+        }
+
+        public static final Parcelable.Creator<ScreenCaptureListener> CREATOR =
+                new Parcelable.Creator<ScreenCaptureListener>() {
+                    @Override
+                    public ScreenCaptureListener createFromParcel(Parcel in) {
+                        return new ScreenCaptureListener(in);
+                    }
+
+                    @Override
+                    public ScreenCaptureListener[] newArray(int size) {
+                        return new ScreenCaptureListener[0];
+                    }
+                };
+    }
+
+    /**
+     * A helper class to handle the async screencapture callbacks synchronously. This should only
+     * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot.
+     */
+    public static class SyncScreenCaptureListener {
+        private static final int SCREENSHOT_WAIT_TIME_S = 1;
+        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private final ScreenCaptureListener mScreenCaptureListener;
+
+        public SyncScreenCaptureListener() {
+            mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> {
+                mScreenshotHardwareBuffer = screenshotHardwareBuffer;
+                mCountDownLatch.countDown();
+            });
+        }
+
+        /**
+         * @return The underlying {@link ScreenCaptureListener}
+         */
+        public ScreenCaptureListener getScreenCaptureListener() {
+            return mScreenCaptureListener;
+        }
+
+        /**
+         * Waits until the screenshot callback has been invoked and the screenshot is ready. This
+         * can return {@code null} if the screenshot callback wasn't invoked after
+         * {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error
+         *
+         * @return A ScreenshotHardwareBuffer for the content that was captured.
+         */
+        @Nullable
+        public ScreenshotHardwareBuffer waitForScreenshot() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to wait for screen capture result", e);
+            }
+
+            return mScreenshotHardwareBuffer;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 83bf801..8d51c9c 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -270,7 +270,6 @@
      */
     void shutdownHotwordDetectionService();
 
-    @EnforcePermission(allOf={"RECORD_AUDIO", "CAPTURE_AUDIO_HOTWORD"})
     void startListeningFromMic(
         in AudioFormat audioFormat,
         in IMicrophoneHotwordDetectionVoiceInteractionCallback callback);
@@ -286,7 +285,6 @@
     /**
      * Test API to simulate to trigger hardware recognition event for test.
      */
-    @EnforcePermission(allOf={"RECORD_AUDIO", "CAPTURE_AUDIO_HOTWORD"})
     void triggerHardwareRecognitionEventForTest(
             in SoundTrigger.KeyphraseRecognitionEvent event,
             in IHotwordRecognitionStatusCallback callback);
diff --git a/core/java/com/android/internal/jank/TEST_MAPPING b/core/java/com/android/internal/jank/TEST_MAPPING
new file mode 100644
index 0000000..4e00ff1
--- /dev/null
+++ b/core/java/com/android/internal/jank/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "com.android.internal.jank"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ],
+      "file_patterns": [
+        "core/java/com/android/internal/jank/.*",
+        "core/tests/coretests/src/com/android/internal/jank/.*"
+      ]
+    }
+  ]
+}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 14a6d5e..d3f9e0a 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -16,6 +16,27 @@
 
 import static android.os.Trace.TRACE_TAG_APP;
 
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +51,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.EventLogTags;
 import com.android.internal.os.BackgroundThread;
 
@@ -103,14 +125,14 @@
     public static final int ACTION_START_RECENTS_ANIMATION = 8;
 
     /**
-     * Time it takes the sensor to detect rotation.
-     */
-    public static final int ACTION_ROTATE_SCREEN_SENSOR = 9;
-
-    /**
      * Time it takes to for the camera based algorithm to rotate the screen.
      */
-    public static final int ACTION_ROTATE_SCREEN_CAMERA_CHECK = 10;
+    public static final int ACTION_ROTATE_SCREEN_CAMERA_CHECK = 9;
+
+    /**
+     * Time it takes the sensor to detect rotation.
+     */
+    public static final int ACTION_ROTATE_SCREEN_SENSOR = 10;
 
     /**
      * Time it takes to start unlock animation .
@@ -143,9 +165,14 @@
     public static final int ACTION_LOAD_SHARE_SHEET = 16;
 
     /**
+     * Time it takes for showing the selection toolbar.
+     */
+    public static final int ACTION_SHOW_SELECTION_TOOLBAR = 17;
+
+    /**
      * Time it takes to show AOD display after folding the device.
      */
-    public static final int ACTION_FOLD_TO_AOD = 17;
+    public static final int ACTION_FOLD_TO_AOD = 18;
 
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
@@ -157,14 +184,15 @@
         ACTION_ROTATE_SCREEN,
         ACTION_FACE_WAKE_AND_UNLOCK,
         ACTION_START_RECENTS_ANIMATION,
-        ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_ROTATE_SCREEN_CAMERA_CHECK,
+        ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_LOCKSCREEN_UNLOCK,
         ACTION_USER_SWITCH,
         ACTION_SWITCH_DISPLAY_UNFOLD,
         ACTION_UDFPS_ILLUMINATE,
         ACTION_SHOW_BACK_ARROW,
         ACTION_LOAD_SHARE_SHEET,
+        ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
     };
 
@@ -179,39 +207,42 @@
         ACTION_ROTATE_SCREEN,
         ACTION_FACE_WAKE_AND_UNLOCK,
         ACTION_START_RECENTS_ANIMATION,
-        ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_ROTATE_SCREEN_CAMERA_CHECK,
+        ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_LOCKSCREEN_UNLOCK,
         ACTION_USER_SWITCH,
         ACTION_SWITCH_DISPLAY_UNFOLD,
         ACTION_UDFPS_ILLUMINATE,
         ACTION_SHOW_BACK_ARROW,
         ACTION_LOAD_SHARE_SHEET,
-        ACTION_FOLD_TO_AOD
+        ACTION_SHOW_SELECTION_TOOLBAR,
+        ACTION_FOLD_TO_AOD,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
     }
 
-    private static final int[] STATSD_ACTION = new int[]{
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
+    @VisibleForTesting
+    public static final int[] STATSD_ACTION = new int[] {
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -269,43 +300,45 @@
     public static String getNameOfAction(int atomsProtoAction) {
         // Defined in AtomsProto.java
         switch (atomsProtoAction) {
-            case 0:
+            case UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION:
                 return "UNKNOWN";
-            case 1:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL:
                 return "ACTION_EXPAND_PANEL";
-            case 2:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS:
                 return "ACTION_TOGGLE_RECENTS";
-            case 3:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK:
                 return "ACTION_FINGERPRINT_WAKE_AND_UNLOCK";
-            case 4:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL:
                 return "ACTION_CHECK_CREDENTIAL";
-            case 5:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED:
                 return "ACTION_CHECK_CREDENTIAL_UNLOCKED";
-            case 6:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN:
                 return "ACTION_TURN_ON_SCREEN";
-            case 7:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN:
                 return "ACTION_ROTATE_SCREEN";
-            case 8:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK:
                 return "ACTION_FACE_WAKE_AND_UNLOCK";
-            case 9:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION:
                 return "ACTION_START_RECENTS_ANIMATION";
-            case 10:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK:
                 return "ACTION_ROTATE_SCREEN_CAMERA_CHECK";
-            case 11:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR:
                 return "ACTION_ROTATE_SCREEN_SENSOR";
-            case 12:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK:
                 return "ACTION_LOCKSCREEN_UNLOCK";
-            case 13:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH:
                 return "ACTION_USER_SWITCH";
-            case 14:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD:
                 return "ACTION_SWITCH_DISPLAY_UNFOLD";
-            case 15:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE:
                 return "ACTION_UDFPS_ILLUMINATE";
-            case 16:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW:
                 return "ACTION_SHOW_BACK_ARROW";
-            case 17:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET:
                 return "ACTION_LOAD_SHARE_SHEET";
-            case 19:
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR:
+                return "ACTION_SHOW_SELECTION_TOOLBAR";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD:
                 return "ACTION_FOLD_TO_AOD";
             default:
                 throw new IllegalArgumentException("Invalid action");
diff --git a/core/java/com/android/internal/util/TEST_MAPPING b/core/java/com/android/internal/util/TEST_MAPPING
index 41d59bb..00a8118 100644
--- a/core/java/com/android/internal/util/TEST_MAPPING
+++ b/core/java/com/android/internal/util/TEST_MAPPING
@@ -15,6 +15,21 @@
         }
       ],
       "file_patterns": ["Xml"]
+    },
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "com.android.internal.util.LatencyTrackerTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ],
+      "file_patterns": ["LatencyTracker.java"]
     }
   ]
 }
diff --git a/core/java/com/android/internal/util/TypedProperties.java b/core/java/com/android/internal/util/TypedProperties.java
index 5613999..c10dbe7 100644
--- a/core/java/com/android/internal/util/TypedProperties.java
+++ b/core/java/com/android/internal/util/TypedProperties.java
@@ -264,29 +264,29 @@
             // Ensure that the type can hold this value, and return.
             int width = (type >> 8) & 0xff;
             switch (width) {
-            case 1:
-                if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
-                    throw new ParseException(st, "8-bit integer constant");
-                }
-                return new Byte((byte)value);
-            case 2:
-                if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
-                    throw new ParseException(st, "16-bit integer constant");
-                }
-                return new Short((short)value);
-            case 4:
-                if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
-                    throw new ParseException(st, "32-bit integer constant");
-                }
-                return new Integer((int)value);
-            case 8:
-                if (value < Long.MIN_VALUE || value > Long.MAX_VALUE) {
-                    throw new ParseException(st, "64-bit integer constant");
-                }
-                return new Long(value);
-            default:
-                throw new IllegalStateException(
-                    "Internal error; unexpected integer type width " + width);
+                case 1:
+                    if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
+                        throw new ParseException(st, "8-bit integer constant");
+                    }
+                    return Byte.valueOf((byte) value);
+                case 2:
+                    if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+                        throw new ParseException(st, "16-bit integer constant");
+                    }
+                    return Short.valueOf((short) value);
+                case 4:
+                    if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
+                        throw new ParseException(st, "32-bit integer constant");
+                    }
+                    return Integer.valueOf((int) value);
+                case 8:
+                    if (value < Long.MIN_VALUE || value > Long.MAX_VALUE) {
+                        throw new ParseException(st, "64-bit integer constant");
+                    }
+                    return Long.valueOf(value);
+                default:
+                    throw new IllegalStateException(
+                            "Internal error; unexpected integer type width " + width);
             }
         } else if ((type & 0xff) == 'F') {
             if (token != StreamTokenizer.TT_WORD) {
@@ -317,10 +317,10 @@
                         throw new ParseException(st, "32-bit float constant");
                     }
                 }
-                return new Float((float)value);
+                return Float.valueOf((float) value);
             } else {
                 // This property is a double; no need to truncate.
-                return new Double(value);
+                return Double.valueOf(value);
             }
         } else if (type == TYPE_STRING) {
             // Expect a quoted string or the word "null".
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 14699e7..a068008 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -22,7 +22,7 @@
 # WindowManager
 per-file android_graphics_BLASTBufferQueue.cpp = file:/services/core/java/com/android/server/wm/OWNERS
 per-file android_view_Surface* = file:/services/core/java/com/android/server/wm/OWNERS
-per-file android_window_WindowInfosListener.cpp = file:/services/core/java/com/android/server/wm/OWNERS
+per-file android_window_* = file:/services/core/java/com/android/server/wm/OWNERS
 
 # Resources
 per-file android_content_res_* = file:/core/java/android/content/res/OWNERS
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 746f88c..77317d1 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2937,13 +2937,14 @@
 
     for (const auto &audioProfile : audioProfiles) {
         jobject jAudioProfile;
-        jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false);
-        if (jStatus == AUDIO_JAVA_BAD_VALUE) {
+        jint jConvertProfileStatus = convertAudioProfileFromNative(
+                                        env, &jAudioProfile, &audioProfile, false);
+        if (jConvertProfileStatus == AUDIO_JAVA_BAD_VALUE) {
             // skipping Java layer unsupported audio formats
             continue;
         }
-        if (jStatus != AUDIO_JAVA_SUCCESS) {
-            return jStatus;
+        if (jConvertProfileStatus != AUDIO_JAVA_SUCCESS) {
+            return jConvertProfileStatus;
         }
         env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile);
         env->DeleteLocalRef(jAudioProfile);
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 3bada15..c1929c6 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -61,9 +61,8 @@
 } gLayerCaptureArgsClassInfo;
 
 static struct {
-    jclass clazz;
-    jmethodID onScreenCaptureComplete;
-} gScreenCaptureListenerClassInfo;
+    jmethodID accept;
+} gConsumerClassInfo;
 
 static struct {
     jclass clazz;
@@ -98,14 +97,14 @@
 public:
     explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
         env->GetJavaVM(&mVm);
-        mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
-        LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
+        mConsumerObject = env->NewGlobalRef(jobject);
+        LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref");
     }
 
     ~ScreenCaptureListenerWrapper() {
-        if (mScreenCaptureListenerObject) {
-            getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
-            mScreenCaptureListenerObject = nullptr;
+        if (mConsumerObject) {
+            getenv()->DeleteGlobalRef(mConsumerObject);
+            mConsumerObject = nullptr;
         }
     }
 
@@ -113,9 +112,8 @@
             const gui::ScreenCaptureResults& captureResults) override {
         JNIEnv* env = getenv();
         if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
-            env->CallVoidMethod(mScreenCaptureListenerObject,
-                                gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
-            checkAndClearException(env, "onScreenCaptureComplete");
+            env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr);
+            checkAndClearException(env, "accept");
             return binder::Status::ok();
         }
         captureResults.fenceResult.value()->waitForever(LOG_TAG);
@@ -130,17 +128,15 @@
                                             captureResults.capturedSecureLayers,
                                             captureResults.capturedHdrLayers);
         checkAndClearException(env, "builder");
-        env->CallVoidMethod(mScreenCaptureListenerObject,
-                            gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
-                            screenshotHardwareBuffer);
-        checkAndClearException(env, "onScreenCaptureComplete");
+        env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer);
+        checkAndClearException(env, "accept");
         env->DeleteLocalRef(jhardwareBuffer);
         env->DeleteLocalRef(screenshotHardwareBuffer);
         return binder::Status::ok();
     }
 
 private:
-    jobject mScreenCaptureListenerObject;
+    jobject mConsumerObject;
     JavaVM* mVm;
 
     JNIEnv* getenv() {
@@ -194,7 +190,7 @@
 }
 
 static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
-                                 jobject screenCaptureListenerObject) {
+                                 jlong screenCaptureListenerObject) {
     const DisplayCaptureArgs captureArgs =
             displayCaptureArgsFromObject(env, displayCaptureArgsObject);
 
@@ -202,13 +198,13 @@
         return BAD_VALUE;
     }
 
-    sp<IScreenCaptureListener> captureListener =
-            sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
+    sp<gui::IScreenCaptureListener> captureListener =
+            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
     return ScreenshotClient::captureDisplay(captureArgs, captureListener);
 }
 
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
-                                jobject screenCaptureListenerObject) {
+                                jlong screenCaptureListenerObject) {
     LayerCaptureArgs captureArgs;
     getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
     SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
@@ -238,21 +234,70 @@
         }
     }
 
-    sp<IScreenCaptureListener> captureListener =
-            sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
+    sp<gui::IScreenCaptureListener> captureListener =
+            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
     return ScreenshotClient::captureLayers(captureArgs, captureListener);
 }
 
+static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
+    sp<gui::IScreenCaptureListener> listener =
+            sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
+    listener->incStrong((void*)nativeCreateScreenCaptureListener);
+    return reinterpret_cast<jlong>(listener.get());
+}
+
+static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
+                                        jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return;
+    }
+    ScreenCaptureListenerWrapper* const self =
+            reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
+    if (self != nullptr) {
+        parcel->writeStrongBinder(IInterface::asBinder(self));
+    }
+}
+
+static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return 0;
+    }
+    sp<gui::IScreenCaptureListener> listener =
+            interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
+    if (listener == nullptr) {
+        return 0;
+    }
+    listener->incStrong((void*)nativeCreateScreenCaptureListener);
+    return reinterpret_cast<jlong>(listener.get());
+}
+
+void destroyNativeListener(void* ptr) {
+    ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
+    listener->decStrong((void*)nativeCreateScreenCaptureListener);
+}
+
+static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sScreenCaptureMethods[] = {
         // clang-format off
-   {"nativeCaptureDisplay",
-            "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
+    {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
             (void*)nativeCaptureDisplay },
-    {"nativeCaptureLayers",
-            "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
+    {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
             (void*)nativeCaptureLayers },
+    {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
+            (void*)nativeCreateScreenCaptureListener },
+    {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
+    {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
+            (void*)nativeReadListenerFromParcel },
+    {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
         // clang-format on
 };
 
@@ -293,12 +338,8 @@
     gLayerCaptureArgsClassInfo.childrenOnly =
             GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
 
-    jclass screenCaptureListenerClazz =
-            FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
-    gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
-    gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
-            GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
-                             "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
+    jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
+    gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
 
     jclass screenshotGraphicsBufferClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dfada13..1d2ce7e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4850,9 +4850,6 @@
         -->
     </array>
 
-    <!-- How long it takes for the HW to start illuminating after the illumination is requested. -->
-    <integer name="config_udfps_illumination_transition_ms">50</integer>
-
     <!-- Indicates whether device has a power button fingerprint sensor. -->
     <bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
 
@@ -5191,7 +5188,7 @@
     <!-- Vertical position of a center of the letterboxed app window.
         0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
         or > 1, it is ignored and central position is used (0.5). -->
-    <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.5</item>
+    <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.0</item>
 
     <!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps.
     -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e8423a..95a81e8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2705,7 +2705,6 @@
   <java-symbol type="bool" name="allow_test_udfps" />
   <java-symbol type="array" name="config_udfps_sensor_props" />
   <java-symbol type="array" name="config_sfps_sensor_props" />
-  <java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
   <java-symbol type="bool" name="config_is_powerbutton_fps" />
   <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
   <java-symbol type="array" name="config_sfps_enroll_stage_thresholds" />
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index ddc05e0..7338c3a 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -204,19 +204,6 @@
         assertEquals(100, Color.alpha(bitmap.getPixel(50, 50)));
     }
 
-    @Test
-    public void testSetBounds() throws Exception {
-        mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
-        mIconDrawable.setBounds(0, 0, 100, 100);
-
-        assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds());
-        assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds());
-
-        mIconDrawable.setBounds(10, 10, 110, 110);
-        assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds());
-        assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds());
-    }
-
     //
     // Utils
     //
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 4770fa6..7674135 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -262,6 +262,8 @@
                         && f.getType() == int.class)
                 .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
 
+        assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
+
         cujs.forEach(f -> {
             final int cuj = getIntFieldChecked(f);
             final String cujName = f.getName();
@@ -279,7 +281,9 @@
                     .that(expectedEnumName.equals(enumName))
                     .isTrue();
             mExpect
-                    .withMessage(formatSimple("getNameOfCuj(%d) not matches %s", cuj, cujName))
+                    .withMessage(
+                            formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
+                                    cuj, cujName, expectedNameOfCuj))
                     .that(cujName.equals(expectedNameOfCuj))
                     .isTrue();
         });
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
new file mode 100644
index 0000000..e384e69
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.util;
+
+import static android.text.TextUtils.formatSimple;
+
+import static com.android.internal.util.LatencyTracker.STATSD_ACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@SmallTest
+public class LatencyTrackerTest {
+    private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
+
+    @Rule
+    public final Expect mExpect = Expect.create();
+
+    @Test
+    public void testCujsMapToEnumsCorrectly() {
+        List<Field> actions = Arrays.stream(LatencyTracker.class.getDeclaredFields())
+                .filter(f -> f.getName().startsWith("ACTION_")
+                        && Modifier.isStatic(f.getModifiers())
+                        && f.getType() == int.class)
+                .collect(Collectors.toList());
+
+        Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
+                .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+                        && Modifier.isStatic(f.getModifiers())
+                        && f.getType() == int.class)
+                .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
+
+        assertThat(enumsMap.size() - 1).isEqualTo(actions.size());
+
+        actions.forEach(f -> {
+            final int action = getIntFieldChecked(f);
+            final String actionName = f.getName();
+            final String expectedEnumName = formatSimple("%s%s", ENUM_NAME_PREFIX, actionName);
+            final int enumKey = STATSD_ACTION[action];
+            final String enumName = enumsMap.get(enumKey);
+            final String expectedActionName = LatencyTracker.getNameOfAction(enumKey);
+            mExpect
+                    .withMessage(formatSimple(
+                            "%s (%d) not matches %s (%d)", actionName, action, enumName, enumKey))
+                    .that(expectedEnumName.equals(enumName))
+                    .isTrue();
+            mExpect
+                    .withMessage(
+                            formatSimple("getNameOfAction(%d) not matches: %s, expected=%s",
+                                    enumKey, actionName, expectedActionName))
+                    .that(actionName.equals(expectedActionName))
+                    .isTrue();
+        });
+    }
+
+    @Test
+    public void testCujTypeEnumCorrectlyDefined() throws Exception {
+        List<Field> cujEnumFields =
+                Arrays.stream(LatencyTracker.class.getDeclaredFields())
+                        .filter(field -> field.getName().startsWith("ACTION_")
+                                && Modifier.isStatic(field.getModifiers())
+                                && field.getType() == int.class)
+                        .collect(Collectors.toList());
+
+        HashSet<Integer> allValues = new HashSet<>();
+        for (Field field : cujEnumFields) {
+            int fieldValue = field.getInt(null);
+            assertWithMessage(
+                    "Field %s must have a mapping to a value in STATSD_ACTION",
+                    field.getName())
+                    .that(fieldValue < STATSD_ACTION.length)
+                    .isTrue();
+            assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
+                    field.getName())
+                    .that(allValues.add(fieldValue))
+                    .isTrue();
+        }
+    }
+
+    private int getIntFieldChecked(Field field) {
+        try {
+            return field.getInt(null);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 47de37d..688425a 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -26,6 +26,8 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -37,6 +39,8 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.PathParser;
@@ -102,8 +106,7 @@
     private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
 
     /**
-     * Unused path.
-     * TODO: Remove once the layoutLib is updated
+     * Clip path defined in R.string.config_icon_mask.
      */
     private static Path sMask;
 
@@ -111,10 +114,9 @@
      * Scaled mask based on the view bounds.
      */
     private final Path mMask;
-    private final Path mMaskTransformed;
+    private final Path mMaskScaleOnly;
     private final Matrix mMaskMatrix;
     private final Region mTransparentRegion;
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
     /**
      * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
@@ -129,10 +131,19 @@
      */
     LayerState mLayerState;
 
+    private Shader mLayersShader;
+    private Bitmap mLayersBitmap;
+
     private final Rect mTmpOutRect = new Rect();
     private Rect mHotspotBounds;
     private boolean mMutated;
 
+    private boolean mSuspendChildInvalidation;
+    private boolean mChildRequestedInvalidation;
+    private final Canvas mCanvas;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+        Paint.FILTER_BITMAP_FLAG);
+
     /**
      * Constructor used for xml inflation.
      */
@@ -146,16 +157,19 @@
      */
     AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
         mLayerState = createConstantState(state, res);
-        // config_icon_mask from context bound resource may have been changed using
+        // config_icon_mask from context bound resource may have been chaged using
         // OverlayManager. Read that one first.
         Resources r = ActivityThread.currentActivityThread() == null
                 ? Resources.getSystem()
                 : ActivityThread.currentActivityThread().getApplication().getResources();
-        mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
-        mMaskTransformed = new Path();
+        // TODO: either make sMask update only when config_icon_mask changes OR
+        // get rid of it all-together in layoutlib
+        sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
+        mMask = new Path(sMask);
+        mMaskScaleOnly = new Path(mMask);
         mMaskMatrix = new Matrix();
+        mCanvas = new Canvas();
         mTransparentRegion = new Region();
-        mPaint.setColor(Color.BLACK);
     }
 
     private ChildDrawable createChildDrawable(Drawable drawable) {
@@ -266,7 +280,7 @@
      * @return the mask path object used to clip the drawable
      */
     public Path getIconMask() {
-        return mMaskTransformed;
+        return mMask;
     }
 
     /**
@@ -308,47 +322,92 @@
         if (bounds.isEmpty()) {
             return;
         }
-        // Set the child layer bounds bigger than the view port size
-        // by {@link #DEFAULT_VIEW_PORT_SCALE}
-        float cX = bounds.exactCenterX();
-        float cY = bounds.exactCenterY();
-        float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2);
-        float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2);
-        final Rect outRect = mTmpOutRect;
-        outRect.set(
-                (int) (cX - insetWidth),
-                (int) (cY - insetHeight),
-                (int) (cX + insetWidth),
-                (int) (cY + insetHeight));
+        updateLayerBounds(bounds);
+    }
+
+    private void updateLayerBounds(Rect bounds) {
+        if (bounds.isEmpty()) {
+            return;
+        }
+        try {
+            suspendChildInvalidation();
+            updateLayerBoundsInternal(bounds);
+            updateMaskBoundsInternal(bounds);
+        } finally {
+            resumeChildInvalidation();
+        }
+    }
+
+    /**
+     * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
+     */
+    private void updateLayerBoundsInternal(Rect bounds) {
+        int cX = bounds.width() / 2;
+        int cY = bounds.height() / 2;
 
         for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
             final ChildDrawable r = mLayerState.mChildren[i];
-            if (r.mDrawable != null) {
-                r.mDrawable.setBounds(outRect);
+            final Drawable d = r.mDrawable;
+            if (d == null) {
+                continue;
             }
+
+            int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
+            int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
+            final Rect outRect = mTmpOutRect;
+            outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
+
+            d.setBounds(outRect);
+        }
+    }
+
+    private void updateMaskBoundsInternal(Rect b) {
+        // reset everything that depends on the view bounds
+        mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
+        sMask.transform(mMaskMatrix, mMaskScaleOnly);
+
+        mMaskMatrix.postTranslate(b.left, b.top);
+        sMask.transform(mMaskMatrix, mMask);
+
+        if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
+                || mLayersBitmap.getHeight() != b.height()) {
+            mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
         }
 
-        // Update the clipping mask
-        mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
-        mMaskMatrix.postTranslate(bounds.left, bounds.top);
-        mMask.transform(mMaskMatrix, mMaskTransformed);
-
-        // Clear the transparent region, it is calculated lazily
+        mPaint.setShader(null);
         mTransparentRegion.setEmpty();
+        mLayersShader = null;
     }
 
     @Override
     public void draw(Canvas canvas) {
-        int saveCount = canvas.save();
-        canvas.clipPath(mMaskTransformed);
-        canvas.drawPaint(mPaint);
-        if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
-            mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas);
+        if (mLayersBitmap == null) {
+            return;
         }
-        if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
-            mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas);
+        if (mLayersShader == null) {
+            mCanvas.setBitmap(mLayersBitmap);
+            mCanvas.drawColor(Color.BLACK);
+            if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
+                mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas);
+            }
+            if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
+                mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas);
+            }
+            mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
+            mPaint.setShader(mLayersShader);
         }
-        canvas.restoreToCount(saveCount);
+        if (mMaskScaleOnly != null) {
+            Rect bounds = getBounds();
+            canvas.translate(bounds.left, bounds.top);
+            canvas.drawPath(mMaskScaleOnly, mPaint);
+            canvas.translate(-bounds.left, -bounds.top);
+        }
+    }
+
+    @Override
+    public void invalidateSelf() {
+        mLayersShader = null;
+        super.invalidateSelf();
     }
 
     @Override
@@ -541,9 +600,37 @@
         return false;
     }
 
+    /**
+     * Temporarily suspends child invalidation.
+     *
+     * @see #resumeChildInvalidation()
+     */
+    private void suspendChildInvalidation() {
+        mSuspendChildInvalidation = true;
+    }
+
+    /**
+     * Resumes child invalidation after suspension, immediately performing an
+     * invalidation if one was requested by a child during suspension.
+     *
+     * @see #suspendChildInvalidation()
+     */
+    private void resumeChildInvalidation() {
+        mSuspendChildInvalidation = false;
+
+        if (mChildRequestedInvalidation) {
+            mChildRequestedInvalidation = false;
+            invalidateSelf();
+        }
+    }
+
     @Override
     public void invalidateDrawable(@NonNull Drawable who) {
-        invalidateSelf();
+        if (mSuspendChildInvalidation) {
+            mChildRequestedInvalidation = true;
+        } else {
+            invalidateSelf();
+        }
     }
 
     @Override
@@ -627,13 +714,6 @@
     @Override
     public void setAlpha(int alpha) {
         mPaint.setAlpha(alpha);
-        final ChildDrawable[] array = mLayerState.mChildren;
-        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
-            final Drawable dr = array[i].mDrawable;
-            if (dr != null) {
-                dr.setAlpha(alpha);
-            }
-        }
     }
 
     @Override
@@ -736,6 +816,10 @@
             }
         }
 
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
         return changed;
     }
 
@@ -751,6 +835,10 @@
             }
         }
 
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
         return changed;
     }
 
@@ -891,7 +979,6 @@
         int mDensity;
 
         // The density to use when inflating/looking up the children drawables. A value of 0 means
-
         // use the system's density.
         int mSrcDensityOverride = 0;
 
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index d9a7994..dbd918e 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -29,6 +29,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
 import java.util.Collection;
 import java.util.Locale;
 
@@ -675,6 +677,26 @@
             }
         }
 
+        /**
+         * @hide
+         */
+        @NonNull public static @DigestEnum
+                AlgorithmParameterSpec fromKeymasterToMGF1ParameterSpec(int digest) {
+            switch (digest) {
+                default:
+                case KeymasterDefs.KM_DIGEST_SHA1:
+                    return MGF1ParameterSpec.SHA1;
+                case KeymasterDefs.KM_DIGEST_SHA_2_224:
+                    return MGF1ParameterSpec.SHA224;
+                case KeymasterDefs.KM_DIGEST_SHA_2_256:
+                    return MGF1ParameterSpec.SHA256;
+                case KeymasterDefs.KM_DIGEST_SHA_2_384:
+                    return MGF1ParameterSpec.SHA384;
+                case KeymasterDefs.KM_DIGEST_SHA_2_512:
+                    return MGF1ParameterSpec.SHA512;
+            }
+        }
+
         @NonNull
         public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) {
             switch (digest) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index e808c5c..7571e44 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -69,6 +69,7 @@
  */
 abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
     private static final String TAG = "AndroidKeyStoreCipherSpiBase";
+    public static final String DEFAULT_MGF1_DIGEST = "SHA-1";
 
     // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
     // doFinal finishes.
@@ -133,24 +134,28 @@
                 if ("RSA/ECB/OAEPWithSHA-224AndMGF1Padding".equals(transform)) {
                     OAEPParameterSpec spec =
                             new OAEPParameterSpec("SHA-224", "MGF1",
-                                    new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+                                    new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST),
+                                    PSource.PSpecified.DEFAULT);
                     mCipher.init(opmode, key, spec, random);
                 } else if ("RSA/ECB/OAEPWithSHA-256AndMGF1Padding".equals(transform)) {
                     OAEPParameterSpec spec =
                             new OAEPParameterSpec("SHA-256", "MGF1",
-                                    new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+                                    new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST),
+                                    PSource.PSpecified.DEFAULT);
                     mCipher.init(opmode, key, spec, random);
 
                 } else if ("RSA/ECB/OAEPWithSHA-384AndMGF1Padding".equals(transform)) {
                     OAEPParameterSpec spec =
                             new OAEPParameterSpec("SHA-384", "MGF1",
-                                    new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+                                    new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST),
+                                    PSource.PSpecified.DEFAULT);
                     mCipher.init(opmode, key, spec, random);
 
                 } else if ("RSA/ECB/OAEPWithSHA-512AndMGF1Padding".equals(transform)) {
                     OAEPParameterSpec spec =
                             new OAEPParameterSpec("SHA-512", "MGF1",
-                                    new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+                                    new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST),
+                                    PSource.PSpecified.DEFAULT);
                     mCipher.init(opmode, key, spec, random);
                 } else {
                     mCipher.init(opmode, key, random);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index cdc1085..acc0005 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -16,6 +16,8 @@
 
 package android.security.keystore2;
 
+import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MGF1_DIGEST;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
@@ -908,6 +910,26 @@
             params.add(KeyStore2ParameterUtils.makeEnum(
                     KeymasterDefs.KM_TAG_PADDING, padding
             ));
+            if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) {
+                final boolean[] hasDefaultMgf1DigestBeenAdded = {false};
+                ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+                    params.add(KeyStore2ParameterUtils.makeEnum(
+                            KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest
+                    ));
+                    hasDefaultMgf1DigestBeenAdded[0] |=
+                            digest.equals(KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST));
+                });
+                /* Because of default MGF1 digest is SHA-1. It has to be added in Key
+                 * characteristics. Otherwise, crypto operations will fail with Incompatible
+                 * MGF1 digest.
+                 */
+                if (!hasDefaultMgf1DigestBeenAdded[0]) {
+                    params.add(KeyStore2ParameterUtils.makeEnum(
+                            KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                            KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
+                    ));
+                }
+            }
         });
         ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
             params.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
index 5848247..e9b66aa 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
@@ -161,10 +161,11 @@
      */
     abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi {
 
-        private static final String MGF_ALGORITGM_MGF1 = "MGF1";
+        private static final String MGF_ALGORITHM_MGF1 = "MGF1";
 
         private int mKeymasterDigest = -1;
         private int mDigestOutputSizeBytes;
+        private int mKeymasterMgf1Digest = KeymasterDefs.KM_DIGEST_SHA1; // Default MGF1 digest
 
         OAEPWithMGF1Padding(int keymasterDigest) {
             super(KeymasterDefs.KM_PAD_RSA_OAEP);
@@ -189,10 +190,10 @@
                         + ". Only OAEPParameterSpec supported");
             }
             OAEPParameterSpec spec = (OAEPParameterSpec) params;
-            if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) {
+            if (!MGF_ALGORITHM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) {
                 throw new InvalidAlgorithmParameterException(
                         "Unsupported MGF: " + spec.getMGFAlgorithm()
-                        + ". Only " + MGF_ALGORITGM_MGF1 + " supported");
+                        + ". Only " + MGF_ALGORITHM_MGF1 + " supported");
             }
             String jcaDigest = spec.getDigestAlgorithm();
             int keymasterDigest;
@@ -225,11 +226,6 @@
             }
             MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams;
             String mgf1JcaDigest = mgfSpec.getDigestAlgorithm();
-            if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) {
-                throw new InvalidAlgorithmParameterException(
-                        "Unsupported MGF1 digest: " + mgf1JcaDigest
-                        + ". Only " + KeyProperties.DIGEST_SHA1 + " supported");
-            }
             PSource pSource = spec.getPSource();
             if (!(pSource instanceof PSource.PSpecified)) {
                 throw new InvalidAlgorithmParameterException(
@@ -244,6 +240,7 @@
                         + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
             }
             mKeymasterDigest = keymasterDigest;
+            mKeymasterMgf1Digest = KeyProperties.Digest.toKeymaster(mgf1JcaDigest);
             mDigestOutputSizeBytes =
                     (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
         }
@@ -273,10 +270,10 @@
         protected final AlgorithmParameters engineGetParameters() {
             OAEPParameterSpec spec =
                     new OAEPParameterSpec(
-                            KeyProperties.Digest.fromKeymaster(mKeymasterDigest),
-                            MGF_ALGORITGM_MGF1,
-                            MGF1ParameterSpec.SHA1,
-                            PSource.PSpecified.DEFAULT);
+                        KeyProperties.Digest.fromKeymaster(mKeymasterDigest),
+                        MGF_ALGORITHM_MGF1,
+                        KeyProperties.Digest.fromKeymasterToMGF1ParameterSpec(mKeymasterMgf1Digest),
+                        PSource.PSpecified.DEFAULT);
             try {
                 AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP");
                 params.init(spec);
@@ -298,6 +295,9 @@
             parameters.add(KeyStore2ParameterUtils.makeEnum(
                     KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
             ));
+            parameters.add(KeyStore2ParameterUtils.makeEnum(
+                    KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest
+            ));
         }
 
         @Override
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index dfda356..94bf122 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -16,6 +16,8 @@
 
 package android.security.keystore2;
 
+import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MGF1_DIGEST;
+
 import android.annotation.NonNull;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.security.keymint.HardwareAuthenticatorType;
@@ -511,6 +513,28 @@
                         KeymasterDefs.KM_TAG_PADDING,
                         padding
                 ));
+                if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) {
+                    if (spec.isDigestsSpecified()) {
+                        boolean hasDefaultMgf1DigestBeenAdded = false;
+                        for (String digest : spec.getDigests()) {
+                            importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                                    KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                    KeyProperties.Digest.toKeymaster(digest)
+                            ));
+                            hasDefaultMgf1DigestBeenAdded |= digest.equals(DEFAULT_MGF1_DIGEST);
+                        }
+                        /* Because of default MGF1 digest is SHA-1. It has to be added in Key
+                         * characteristics. Otherwise, crypto operations will fail with Incompatible
+                         * MGF1 digest.
+                         */
+                        if (!hasDefaultMgf1DigestBeenAdded) {
+                            importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                                    KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                    KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
+                            ));
+                        }
+                    }
+                }
             }
             for (String padding : spec.getSignaturePaddings()) {
                 importArgs.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
index dcdd7de..54955c6 100644
--- a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
@@ -78,6 +78,7 @@
                 kp.value = KeyParameterValue.blockMode(v);
                 break;
             case Tag.DIGEST:
+            case Tag.RSA_OAEP_MGF_DIGEST:
                 kp.value = KeyParameterValue.digest(v);
                 break;
             case Tag.EC_CURVE:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index a8764e0..d76ad3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -517,7 +517,9 @@
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
     }
 
-    ActivityManager.RunningTaskInfo getTaskInfo() {
+    /** Returns the task info for the task in the TaskView. */
+    @Nullable
+    public ActivityManager.RunningTaskInfo getTaskInfo() {
         return mTaskInfo;
     }
 
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 c39602032..7c3c14e 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
@@ -55,6 +55,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -477,11 +479,12 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
                 RecentTasksController.create(context, shellInit, shellCommandHandler,
-                        taskStackListener, mainExecutor));
+                        taskStackListener, desktopModeTaskRepository, mainExecutor));
     }
 
     //
@@ -666,6 +669,24 @@
     }
 
     //
+    // Desktop mode (optional feature)
+    //
+
+    @BindsOptionalOf
+    @DynamicOverride
+    abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
+
+    @WMSingleton
+    @Provides
+    static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository(
+            @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
+        if (DesktopMode.IS_SUPPORTED) {
+            return desktopModeTaskRepository;
+        }
+        return Optional.empty();
+    }
+
+    //
     // Misc
     //
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 18ce364..27d3e35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -220,14 +221,14 @@
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<RecentTasksController> recentTasksController,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             WindowDecorViewModel<?> windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
         ShellInit init = FreeformComponents.isFreeformEnabled(context)
                 ? shellInit
                 : null;
-        return new FreeformTaskListener<>(init, shellTaskOrganizer, recentTasksController,
+        return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
                 windowDecorViewModel);
     }
 
@@ -599,17 +600,24 @@
             Context context, ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
-            @ShellMainThread Handler mainHandler
+            @ShellMainThread Handler mainHandler,
+            Transitions transitions
     ) {
         if (DesktopMode.IS_SUPPORTED) {
             return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
-                    rootDisplayAreaOrganizer,
-                    mainHandler));
+                    rootDisplayAreaOrganizer, mainHandler, transitions));
         } else {
             return Optional.empty();
         }
     }
 
+    @WMSingleton
+    @Provides
+    @DynamicOverride
+    static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
+        return new DesktopModeTaskRepository();
+    }
+
     //
     // Misc
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 7d34ea4..c07ce10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 
@@ -37,6 +38,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 /**
  * Handles windowing changes when desktop mode system setting changes
@@ -47,15 +49,18 @@
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
     private final SettingsObserver mSettingsObserver;
+    private final Transitions mTransitions;
 
     public DesktopModeController(Context context, ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
-            @ShellMainThread Handler mainHandler) {
+            @ShellMainThread Handler mainHandler,
+            Transitions transitions) {
         mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
         mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
         mSettingsObserver = new SettingsObserver(mContext, mainHandler);
+        mTransitions = transitions;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -89,7 +94,11 @@
         }
         wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
                 targetWindowingMode), true /* transfer */);
-        mRootDisplayAreaOrganizer.applyTransaction(wct);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+        } else {
+            mRootDisplayAreaOrganizer.applyTransaction(wct);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
new file mode 100644
index 0000000..988601c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.desktopmode
+
+import android.util.ArraySet
+
+/**
+ * Keeps track of task data related to desktop mode.
+ */
+class DesktopModeTaskRepository {
+
+    /**
+     * Set of task ids that are marked as active in desktop mode.
+     * Active tasks in desktop mode are freeform tasks that are visible or have been visible after
+     * desktop mode was activated.
+     * Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
+     */
+    private val activeTasks = ArraySet<Int>()
+    private val listeners = ArraySet<Listener>()
+
+    /**
+     * Add a [Listener] to be notified of updates to the repository.
+     */
+    fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    /**
+     * Remove a previously registered [Listener]
+     */
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    /**
+     * Mark a task with given [taskId] as active.
+     */
+    fun addActiveTask(taskId: Int) {
+        val added = activeTasks.add(taskId)
+        if (added) {
+            listeners.onEach { it.onActiveTasksChanged() }
+        }
+    }
+
+    /**
+     * Remove task with given [taskId] from active tasks.
+     */
+    fun removeActiveTask(taskId: Int) {
+        val removed = activeTasks.remove(taskId)
+        if (removed) {
+            listeners.onEach { it.onActiveTasksChanged() }
+        }
+    }
+
+    /**
+     * Check if a task with the given [taskId] was marked as an active task
+     */
+    fun isActiveTask(taskId: Int): Boolean {
+        return activeTasks.contains(taskId)
+    }
+
+    /**
+     * Get a set of the active tasks
+     */
+    fun getActiveTasks(): ArraySet<Int> {
+        return ArraySet(activeTasks)
+    }
+
+    /**
+     * Defines interface for classes that can listen to changes in repository state.
+     */
+    interface Listener {
+        fun onActiveTasksChanged()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 435d8ea..62bf517 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -17,6 +17,8 @@
 package com.android.wm.shell.draganddrop;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -263,6 +265,9 @@
             mStarter.startShortcut(packageName, id, position, opts, user);
         } else {
             final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+            // Put BAL flags to avoid activity start aborted.
+            opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+            opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
             mStarter.startIntent(launchIntent, null /* fillIntent */, position, opts);
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 1baac71..f58719b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,8 +29,8 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -49,7 +49,7 @@
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<RecentTasksController> mRecentTasksOptional;
+    private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final WindowDecorViewModel<T> mWindowDecorationViewModel;
 
     private final SparseArray<State<T>> mTasks = new SparseArray<>();
@@ -64,11 +64,11 @@
     public FreeformTaskListener(
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<RecentTasksController> recentTasksController,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             WindowDecorViewModel<T> windowDecorationViewModel) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
-        mRecentTasksOptional = recentTasksController;
+        mDesktopModeTaskRepository = desktopModeTaskRepository;
         if (shellInit != null) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -93,7 +93,7 @@
         if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Adding active freeform task: #%d", taskInfo.taskId);
-            mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId));
+            mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
         }
     }
 
@@ -126,7 +126,7 @@
         if (DesktopMode.IS_SUPPORTED) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Removing active freeform task: #%d", taskInfo.taskId);
-            mRecentTasksOptional.ifPresent(rt -> rt.removeActiveFreeformTask(taskInfo.taskId));
+            mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
         }
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -154,7 +154,7 @@
             if (taskInfo.isVisible) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                         "Adding active freeform task: #%d", taskInfo.taskId);
-                mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId));
+                mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
             }
         }
     }
@@ -191,15 +191,19 @@
      *
      * @param change the change of this task transition that needs to have the task layer as the
      *               leash
-     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
+     * @return {@code true} if it creates the window decoration; {@code false} otherwise
      */
-    void createWindowDecoration(
+    boolean createWindowDecoration(
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
         final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
+        if (state.mWindowDecoration != null) {
+            return false;
+        }
         state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
                 state.mTaskInfo, state.mLeash, startT, finishT);
+        return true;
     }
 
     /**
@@ -222,6 +226,9 @@
             windowDecor =
                     mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
         }
+        if (windowDecor == null) {
+            return null;
+        }
         mWindowDecorationViewModel.setupWindowDecorationForTransition(
                 taskInfo, startT, finishT, windowDecor);
         return windowDecor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index a780ec1..17d6067 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -26,6 +26,7 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -80,6 +81,7 @@
             @NonNull SurfaceControl.Transaction startT,
             @NonNull SurfaceControl.Transaction finishT) {
         final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
+        final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
         for (TransitionInfo.Change change : info.getChanges()) {
             if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                 continue;
@@ -89,9 +91,22 @@
             if (taskInfo == null || taskInfo.taskId == -1) {
                 continue;
             }
+            // Filter out non-leaf tasks. Freeform/fullscreen don't nest tasks, but split-screen
+            // does, so this prevents adding duplicate captions in that scenario.
+            if (change.getParent() != null
+                    && info.getChange(change.getParent()).getTaskInfo() != null) {
+                // This logic relies on 2 assumptions: 1 is that child tasks will be visited before
+                // parents (due to how z-order works). 2 is that no non-tasks are interleaved
+                // between tasks (hierarchically).
+                taskParents.add(change.getContainer());
+            }
+            if (taskParents.contains(change.getContainer())) {
+                continue;
+            }
 
             switch (change.getMode()) {
                 case WindowManager.TRANSIT_OPEN:
+                case WindowManager.TRANSIT_TO_FRONT:
                     onOpenTransitionReady(change, startT, finishT);
                     break;
                 case WindowManager.TRANSIT_CLOSE: {
@@ -154,20 +169,28 @@
 
         boolean adopted = false;
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_MAXIMIZE
-                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
             windowDecor = mFreeformTaskListener.giveWindowDecoration(
                     change.getTaskInfo(), startT, finishT);
-            adopted = mFullscreenTaskListener.adoptWindowDecoration(
-                    change, startT, finishT, windowDecor);
+            if (windowDecor != null) {
+                adopted = mFullscreenTaskListener.adoptWindowDecoration(
+                        change, startT, finishT, windowDecor);
+            } else {
+                // will return false if it already has the window decor.
+                adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
+            }
         }
 
-        if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE
-                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             windowDecor = mFullscreenTaskListener.giveWindowDecoration(
                     change.getTaskInfo(), startT, finishT);
-            adopted = mFreeformTaskListener.adoptWindowDecoration(
-                    change, startT, finishT, windowDecor);
+            if (windowDecor != null) {
+                adopted = mFreeformTaskListener.adoptWindowDecoration(
+                        change, startT, finishT, windowDecor);
+            } else {
+                // will return false if it already has the window decor.
+                adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
+            }
         }
 
         if (!adopted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index e9f9bb5..76e296b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.fullscreen;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
 
@@ -107,13 +105,14 @@
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         updateRecentsForVisibleFullscreenTask(taskInfo);
-        if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) {
+        if (mWindowDecorViewModelOptional.isPresent()) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             state.mWindowDecoration =
                     mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
                             leash, t, t);
             t.apply();
-        } else {
+        }
+        if (state.mWindowDecoration == null) {
             mSyncQueue.runInSync(t -> {
                 // Reset several properties back to fullscreen (PiP, for example, leaves all these
                 // properties in a bad state).
@@ -174,17 +173,23 @@
      *
      * @param change the change of this task transition that needs to have the task layer as the
      *               leash
+     * @return {@code true} if a decoration was actually created.
      */
-    public void createWindowDecoration(TransitionInfo.Change change,
+    public boolean createWindowDecoration(TransitionInfo.Change change,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
         final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        if (!mWindowDecorViewModelOptional.isPresent()
-                || !shouldShowWindowDecor(state.mTaskInfo)) {
-            return;
+        if (!mWindowDecorViewModelOptional.isPresent()) return false;
+        if (state.mWindowDecoration != null) {
+            // Already has a decoration.
+            return false;
         }
-
-        state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration(
+        T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
                 state.mTaskInfo, state.mLeash, startT, finishT);
+        if (newWindowDecor != null) {
+            state.mWindowDecoration = newWindowDecor;
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -202,8 +207,7 @@
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT,
             @Nullable AutoCloseable windowDecor) {
-        if (!mWindowDecorViewModelOptional.isPresent()
-                || !shouldShowWindowDecor(change.getTaskInfo())) {
+        if (!mWindowDecorViewModelOptional.isPresent()) {
             return false;
         }
         final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
@@ -214,8 +218,11 @@
                     state.mTaskInfo, startT, finishT, state.mWindowDecoration);
             return true;
         } else {
-            state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration(
+            T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
                     state.mTaskInfo, state.mLeash, startT, finishT);
+            if (newWindowDecor != null) {
+                state.mWindowDecoration = newWindowDecor;
+            }
             return false;
         }
     }
@@ -256,7 +263,7 @@
             windowDecor =
                     mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
         }
-        if (mWindowDecorViewModelOptional.isPresent()) {
+        if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) {
             mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
                     taskInfo, startT, finishT, windowDecor);
         }
@@ -336,10 +343,5 @@
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
     }
 
-    private static boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
-        return taskInfo.getConfiguration().windowConfiguration.getDisplayWindowingMode()
-                == WINDOWING_MODE_FREEFORM;
-    }
-
 
 }
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..1a52d8c 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
@@ -324,6 +324,19 @@
         return mPipTransitionController;
     }
 
+    /**
+     * Returns true if the PiP window is currently being animated.
+     */
+    public boolean isAnimating() {
+        // TODO(b/183746978) move this to PipAnimationController, and inject that in PipController
+        PipAnimationController.PipTransitionAnimator animator =
+                mPipAnimationController.getCurrentAnimator();
+        if (animator != null && animator.isRunning()) {
+            return true;
+        }
+        return false;
+    }
+
     public Rect getCurrentOrAnimatingBounds() {
         PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getCurrentAnimator();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 6dd02e4..84071e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -54,13 +54,8 @@
                 ? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
                 : pipBoundsState.getBounds();
         float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
-        int verticalGravity;
+        int verticalGravity = Gravity.BOTTOM;
         int horizontalGravity;
-        if (snapFraction < 1.5f || snapFraction >= 3.5f) {
-            verticalGravity = Gravity.NO_GRAVITY;
-        } else {
-            verticalGravity = Gravity.BOTTOM;
-        }
         if (snapFraction >= 0.5f && snapFraction < 2.5f) {
             horizontalGravity = Gravity.RIGHT;
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 3d879b6..bc8191d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -149,7 +149,42 @@
     private final Rect mTmpInsetBounds = new Rect();
     private final int mEnterAnimationDuration;
 
-    private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback;
+    private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback =
+            this::onKeepClearAreasChangedCallback;
+
+    private void onKeepClearAreasChangedCallback() {
+        if (!mEnablePipKeepClearAlgorithm) {
+            // early bail out if the keep clear areas feature is disabled
+            return;
+        }
+        // if there is another animation ongoing, wait for it to finish and try again
+        if (mPipTaskOrganizer.isAnimating()) {
+            mMainExecutor.removeCallbacks(
+                    mMovePipInResponseToKeepClearAreasChangeCallback);
+            mMainExecutor.executeDelayed(
+                    mMovePipInResponseToKeepClearAreasChangeCallback,
+                    PIP_KEEP_CLEAR_AREAS_DELAY);
+            return;
+        }
+        updatePipPositionForKeepClearAreas();
+    }
+
+    private void updatePipPositionForKeepClearAreas() {
+        if (!mEnablePipKeepClearAlgorithm) {
+            // early bail out if the keep clear areas feature is disabled
+            return;
+        }
+        // only move if already in pip, other transitions account for keep clear areas
+        if (mPipTransitionState.hasEnteredPip()) {
+            Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
+                    mPipBoundsAlgorithm);
+            // only move if the bounds are actually different
+            if (destBounds != mPipBoundsState.getBounds()) {
+                mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
+                        mEnterAnimationDuration, null);
+            }
+        }
+    }
 
     private boolean mIsInFixedRotation;
     private PipAnimationListener mPinnedStackAnimationRecentsCallback;
@@ -302,6 +337,9 @@
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
             mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
+            if (imeVisible) {
+                updatePipPositionForKeepClearAreas();
+            }
         }
 
         @Override
@@ -414,15 +452,6 @@
 
         mEnterAnimationDuration = mContext.getResources()
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
-        mMovePipInResponseToKeepClearAreasChangeCallback = () -> {
-            // only move if already in pip, other transitions account for keep clear areas
-            if (mPipTransitionState.hasEnteredPip()) {
-                Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
-                        mPipBoundsAlgorithm);
-                mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
-                        mEnterAnimationDuration, null);
-            }
-        };
         mPipParamsChangedForwarder = pipParamsChangedForwarder;
         mDisplayInsetsController = displayInsetsController;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 84d9217..1f3f31e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -34,6 +34,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.util.Size;
 import android.view.DisplayCutout;
@@ -70,6 +71,9 @@
     private static final String TAG = "PipTouchHandler";
     private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
 
+    private static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+            SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
     // Allow PIP to resize to a slightly bigger state upon touch
     private boolean mEnableResize;
     private final Context mContext;
@@ -426,6 +430,9 @@
             if (mTouchState.isUserInteracting()) {
                 // Defer the update of the current movement bounds until after the user finishes
                 // touching the screen
+            } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+                // Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height
+                // now are accounted for in the keep clear algorithm calculations
             } else {
                 final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
                 final Rect toMovementBounds = new Rect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 27bc1a1..ff4b2ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellInit;
@@ -53,19 +54,20 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
  */
 public class RecentTasksController implements TaskStackListenerCallback,
-        RemoteCallable<RecentTasksController> {
+        RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener {
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
     private final ShellCommandHandler mShellCommandHandler;
+    private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasks mImpl = new RecentTasksImpl();
@@ -84,15 +86,6 @@
     private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>();
 
     /**
-     * Set of taskId's that have been launched in freeform mode.
-     * This includes tasks that are currently running, visible and in freeform mode. And also
-     * includes tasks that are running in the background, are no longer visible, but at some point
-     * were visible to the user.
-     * This is used to decide which freeform apps belong to the user's desktop.
-     */
-    private final HashSet<Integer> mActiveFreeformTasks = new HashSet<>();
-
-    /**
      * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
      * supported.
      */
@@ -102,24 +95,27 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
             return null;
         }
         return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
-                mainExecutor);
+                desktopModeTaskRepository, mainExecutor);
     }
 
     RecentTasksController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
+        mDesktopModeTaskRepository = desktopModeTaskRepository;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -131,6 +127,7 @@
     private void onInit() {
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mTaskStackListener.addListener(this);
+        mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
     }
 
     /**
@@ -217,19 +214,8 @@
         notifyRecentTasksChanged();
     }
 
-    /**
-     * Mark a task with given {@code taskId} as active in freeform
-     */
-    public void addActiveFreeformTask(int taskId) {
-        mActiveFreeformTasks.add(taskId);
-        notifyRecentTasksChanged();
-    }
-
-    /**
-     * Remove task with given {@code taskId} from active freeform tasks
-     */
-    public void removeActiveFreeformTask(int taskId) {
-        mActiveFreeformTasks.remove(taskId);
+    @Override
+    public void onActiveTasksChanged() {
         notifyRecentTasksChanged();
     }
 
@@ -312,7 +298,8 @@
                 continue;
             }
 
-            if (desktopModeActive && mActiveFreeformTasks.contains(taskInfo.taskId)) {
+            if (desktopModeActive && mDesktopModeTaskRepository.isPresent()
+                    && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
                 // Freeform tasks will be added as a separate entry
                 freeformTasks.add(taskInfo);
                 continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 025e559..3758471 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -154,7 +154,8 @@
     private StageCoordinator mStageCoordinator;
     // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
     // outside the bounds of the roots by being reparented into a higher level fullscreen container
-    private SurfaceControl mSplitTasksContainerLayer;
+    private SurfaceControl mGoingToRecentsTasksLayer;
+    private SurfaceControl mStartingSplitTasksLayer;
 
     public SplitScreenController(Context context,
             ShellInit shellInit,
@@ -509,19 +510,53 @@
     }
 
     RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
+        if (ENABLE_SHELL_TRANSITIONS) return null;
+
         if (isSplitScreenVisible()) {
             // Evict child tasks except the top visible one under split root to ensure it could be
             // launched as full screen when switching to it on recents.
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             mStageCoordinator.prepareEvictInvisibleChildTasks(wct);
             mSyncQueue.queue(wct);
+        } else {
+            return null;
         }
-        return reparentSplitTasksForAnimation(apps, false /* enterSplitScreen */);
+
+        SurfaceControl.Transaction t = mTransactionPool.acquire();
+        if (mGoingToRecentsTasksLayer != null) {
+            t.remove(mGoingToRecentsTasksLayer);
+        }
+        mGoingToRecentsTasksLayer = reparentSplitTasksForAnimation(apps, t,
+                "SplitScreenController#onGoingToRecentsLegacy" /* callsite */);
+        t.apply();
+        mTransactionPool.release(t);
+
+        return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
     }
 
     RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
+        if (ENABLE_SHELL_TRANSITIONS) return null;
+
+        int openingApps = 0;
+        for (int i = 0; i < apps.length; ++i) {
+            if (apps[i].mode == MODE_OPENING) openingApps++;
+        }
+        if (openingApps < 2) {
+            // Not having enough apps to enter split screen
+            return null;
+        }
+
+        SurfaceControl.Transaction t = mTransactionPool.acquire();
+        if (mStartingSplitTasksLayer != null) {
+            t.remove(mStartingSplitTasksLayer);
+        }
+        mStartingSplitTasksLayer = reparentSplitTasksForAnimation(apps, t,
+                "SplitScreenController#onStartingSplitLegacy" /* callsite */);
+        t.apply();
+        mTransactionPool.release(t);
+
         try {
-            return reparentSplitTasksForAnimation(apps, true /* enterSplitScreen */);
+            return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
         } finally {
             for (RemoteAnimationTarget appTarget : apps) {
                 if (appTarget.leash != null) {
@@ -531,45 +566,23 @@
         }
     }
 
-    private RemoteAnimationTarget[] reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps,
-            boolean enterSplitScreen) {
-        if (ENABLE_SHELL_TRANSITIONS) return null;
-
-        if (enterSplitScreen) {
-            int openingApps = 0;
-            for (int i = 0; i < apps.length; ++i) {
-                if (apps[i].mode == MODE_OPENING) openingApps++;
-            }
-            if (openingApps < 2) {
-                // Not having enough apps to enter split screen
-                return null;
-            }
-        } else if (!isSplitScreenVisible()) {
-            return null;
-        }
-
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-        if (mSplitTasksContainerLayer != null) {
-            // Remove the previous layer before recreating
-            transaction.remove(mSplitTasksContainerLayer);
-        }
+    private SurfaceControl reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps,
+            SurfaceControl.Transaction t, String callsite) {
         final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
                 .setContainerLayer()
                 .setName("RecentsAnimationSplitTasks")
                 .setHidden(false)
-                .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+                .setCallsite(callsite);
         mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
-        mSplitTasksContainerLayer = builder.build();
+        final SurfaceControl splitTasksLayer = builder.build();
 
         for (int i = 0; i < apps.length; ++i) {
             final RemoteAnimationTarget appTarget = apps[i];
-            transaction.reparent(appTarget.leash, mSplitTasksContainerLayer);
-            transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+            t.reparent(appTarget.leash, splitTasksLayer);
+            t.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
                     appTarget.screenSpaceBounds.top);
         }
-        transaction.apply();
-        mTransactionPool.release(transaction);
-        return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
+        return splitTasksLayer;
     }
     /**
      * Sets drag info to be logged when splitscreen is entered.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index db35b48..25793ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -1109,9 +1107,6 @@
 
     private void addActivityOptions(Bundle opts, StageTaskListener stage) {
         opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
-        // Put BAL flags to avoid activity start aborted.
-        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
-        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
     }
 
     void updateActivityOptions(Bundle opts, @SplitPosition int position) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 83aa539..e8a2cb160 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.transition.Transitions;
 
@@ -80,6 +81,7 @@
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
+        if (!shouldShowWindowDecor(taskInfo)) return null;
         final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
                 mContext,
                 mDisplayController,
@@ -101,9 +103,12 @@
 
     @Override
     public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
-        return (windowDecor instanceof CaptionWindowDecoration)
-                ? (CaptionWindowDecoration) windowDecor
-                : null;
+        if (!(windowDecor instanceof CaptionWindowDecoration)) return null;
+        final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor;
+        if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) {
+            return null;
+        }
+        return captionWindowDecor;
     }
 
     @Override
@@ -231,4 +236,11 @@
             }
         }
     }
+
+    private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
+        return DesktopMode.IS_SUPPORTED
+                && mDisplayController.getDisplayContext(taskInfo.displayId)
+                .getResources().getConfiguration().smallestScreenWidthDp >= 600;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index c234949..d9697d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -42,6 +42,7 @@
 
     /**
      * Creates a window decoration for the given task.
+     * Can be {@code null} for Fullscreen tasks but not Freeform ones.
      *
      * @param taskInfo the initial task info of the task
      * @param taskSurface the surface of the task
@@ -49,7 +50,7 @@
      * @param finishT the finish transaction to restore states after the transition
      * @return the window decoration object
      */
-    T createWindowDecoration(
+    @Nullable T createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
@@ -57,11 +58,12 @@
 
     /**
      * Adopts the window decoration if possible.
+     * May be {@code null} if a window decor is not needed or the given one is incompatible.
      *
      * @param windowDecor the potential window decoration to adopt
      * @return the window decoration if it can be adopted, or {@code null} otherwise.
      */
-    T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
+    @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
 
     /**
      * Notifies a task info update on the given task, with the window decoration created previously
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 06361f9..53dd8b0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -24,3 +24,7 @@
 val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
 val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
 val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+enum class Direction {
+    UP, DOWN, LEFT, RIGHT
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 0f7d8a9..39be89d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -30,7 +30,7 @@
  */
 abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = true) {
+        get() = buildTransition {
             setup {
                 this.setRotation(testSpec.startRotation)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 53450de..c2fd0d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -62,7 +62,7 @@
      * Defines the transition used to run the test
      */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = true) {
+        get() = buildTransition {
             setup {
                 // launch an app behind the pip one
                 testApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 75018d1..0d75e02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -62,7 +62,7 @@
      * Defines the transition used to run the test
      */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = true) {
+        get() = buildTransition {
             setup {
                 // launch an app behind the pip one
                 testApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 4d39ec5..513ce95 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -56,7 +56,7 @@
 @Group3
 class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = true) {
+        get() = buildTransition {
             transitions {
                 pipApp.doubleClickPipWindow(wmHelper)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index de8c0093..746ce91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -16,28 +16,30 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
-import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
 /**
- * Test Pip movement with Launcher shelf height change (decrease).
+ * Test Pip movement with Launcher shelf height change (increase).
  *
- * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
  *
  * Actions:
  *     Launch [pipApp] in pip mode
- *     Launch [testApp]
  *     Press home
+ *     Launch [testApp]
  *     Check if pip window moves down (visually)
  *
  * Notes:
@@ -53,26 +55,41 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group3
-open class MovePipDownShelfHeightChangeTest(
+class MovePipDownShelfHeightChangeTest(
     testSpec: FlickerTestParameter
 ) : MovePipShelfHeightTransition(testSpec) {
+//    @Before
+//    fun before() {
+//        Assume.assumeFalse(isShellTransitionsEnabled)
+//    }
+
     /**
      * Defines the transition used to run the test
      */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = false) {
+        get() = buildTransition {
             teardown {
-                testApp.launchViaIntent(wmHelper)
+                tapl.pressHome()
                 testApp.exit(wmHelper)
             }
             transitions {
-                tapl.pressHome()
+                testApp.launchViaIntent(wmHelper)
             }
         }
 
-    override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) {
-        current.isHigherOrEqual(previous.region)
-    }
+    /**
+     * Checks that the visible region of [pipApp] window always moves down during the animation.
+     */
+    @Presubmit
+    @Test
+    fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+
+    /**
+     * Checks that the visible region of [pipApp] layer always moves down during the animation.
+     */
+    @Presubmit
+    @Test
+    fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 9fb941e..19bdca5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -19,6 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.wm.shell.flicker.Direction
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Test
 
@@ -31,11 +32,6 @@
     protected val testApp = FixedAppHelper(instrumentation)
 
     /**
-     * Checks if the window movement direction is valid
-     */
-    protected abstract fun assertRegionMovement(previous: RegionSubject, current: RegionSubject)
-
-    /**
      * Checks [pipApp] window remains visible throughout the animation
      */
     @Presubmit
@@ -82,31 +78,46 @@
     }
 
     /**
-     * Checks that the visible region of [pipApp] always moves in the correct direction
+     * Checks that the visible region of [pipApp] window always moves in the specified direction
      * during the animation.
      */
-    @Presubmit
-    @Test
-    open fun pipWindowMoves() {
+    protected fun pipWindowMoves(direction: Direction) {
         testSpec.assertWm {
-            val pipWindowList = this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }
-            pipWindowList.zipWithNext { previous, current ->
-                assertRegionMovement(previous.frame, current.frame)
+            val pipWindowFrameList = this.windowStates {
+                pipApp.windowMatchesAnyOf(it) && it.isVisible
+            }.map { it.frame }
+            when (direction) {
+                Direction.UP -> assertRegionMovementUp(pipWindowFrameList)
+                Direction.DOWN -> assertRegionMovementDown(pipWindowFrameList)
+                else -> error("Unhandled direction")
             }
         }
     }
 
     /**
-     * Checks that the visible region of [pipApp] always moves up during the animation
+     * Checks that the visible region of [pipApp] layer always moves in the specified direction
+     * during the animation.
      */
-    @Presubmit
-    @Test
-    open fun pipLayerMoves() {
+    protected fun pipLayerMoves(direction: Direction) {
         testSpec.assertLayers {
-            val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
-            pipLayerList.zipWithNext { previous, current ->
-                assertRegionMovement(previous.visibleRegion, current.visibleRegion)
+            val pipLayerRegionList = this.layers {
+                pipApp.layerMatchesAnyOf(it) && it.isVisible
+            }.map { it.visibleRegion }
+            when (direction) {
+                Direction.UP -> assertRegionMovementUp(pipLayerRegionList)
+                Direction.DOWN -> assertRegionMovementDown(pipLayerRegionList)
+                else -> error("Unhandled direction")
             }
         }
     }
+
+    private fun assertRegionMovementDown(regions: List<RegionSubject>) {
+        regions.zipWithNext { previous, current -> current.isLowerOrEqual(previous) }
+        regions.last().isLower(regions.first())
+    }
+
+    private fun assertRegionMovementUp(regions: List<RegionSubject>) {
+        regions.zipWithNext { previous, current -> current.isHigherOrEqual(previous.region) }
+        regions.last().isHigher(regions.first())
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 3e08bfb..93e7d5c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -16,31 +16,30 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
+import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.traces.region.RegionSubject
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
 /**
- * Test Pip movement with Launcher shelf height change (increase).
+ * Test Pip movement with Launcher shelf height change (decrease).
  *
- * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
  *
  * Actions:
  *     Launch [pipApp] in pip mode
- *     Press home
  *     Launch [testApp]
+ *     Press home
  *     Check if pip window moves up (visually)
  *
  * Notes:
@@ -56,31 +55,38 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group3
-class MovePipUpShelfHeightChangeTest(
+open class MovePipUpShelfHeightChangeTest(
     testSpec: FlickerTestParameter
 ) : MovePipShelfHeightTransition(testSpec) {
-    @Before
-    fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /**
      * Defines the transition used to run the test
      */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = false) {
-            teardown {
-                tapl.pressHome()
-                testApp.exit(wmHelper)
+        get() = buildTransition() {
+            setup {
+                testApp.launchViaIntent(wmHelper)
             }
             transitions {
-                testApp.launchViaIntent(wmHelper)
+                tapl.pressHome()
+            }
+            teardown {
+                testApp.exit(wmHelper)
             }
         }
 
-    override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) {
-        current.isLowerOrEqual(previous.region)
-    }
+    /**
+     * Checks that the visible region of [pipApp] window always moves up during the animation.
+     */
+    @Presubmit
+    @Test
+    fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
+
+    /**
+     * Checks that the visible region of [pipApp] layer always moves up during the animation.
+     */
+    @Presubmit
+    @Test
+    fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 27fc2ed..3d3b53d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -56,7 +56,7 @@
 
     /** {@inheritDoc}  */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = false) {
+        get() = buildTransition {
             setup {
                 imeApp.launchViaIntent(wmHelper)
                 setRotation(testSpec.startRotation)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index fd5ff29..f13698f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -72,7 +72,7 @@
     }
 
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = false) {
+        get() = buildTransition {
             setup {
                 fixedApp.launchViaIntent(wmHelper)
                 setRotation(testSpec.startRotation)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index faf6afc..b67cf77 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -65,7 +65,6 @@
      */
     @JvmOverloads
     protected open fun buildTransition(
-        eachRun: Boolean,
         stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
         extraSpec: FlickerBuilder.() -> Unit = {}
     ): FlickerBuilder.() -> Unit {
@@ -73,25 +72,12 @@
             setup {
                 setRotation(Surface.ROTATION_0)
                 removeAllTasksButHome()
-
-                if (!eachRun) {
-                    pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
-                }
-                if (eachRun) {
-                    pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
-                }
+                pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
             }
             teardown {
                 setRotation(Surface.ROTATION_0)
                 removeAllTasksButHome()
                 pipApp.exit(wmHelper)
-
-                if (eachRun) {
-                    pipApp.exit(wmHelper)
-                }
-                if (!eachRun) {
-                    pipApp.exit(wmHelper)
-                }
             }
 
             extraSpec(this)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 4e8773f..2db3009 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
@@ -75,13 +76,13 @@
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    // TODO(b/245472831): Move back to presubmit after shell transitions landing.
+    @FlakyTest(bugId = 245472831)
     @Test
     fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    // TODO(b/245472831): Move back to presubmit after shell transitions landing.
+    @FlakyTest(bugId = 245472831)
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index e174f5e..2d5d2b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
@@ -85,8 +86,9 @@
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    // TODO(b/246490534): Move back to presubmit after withAppTransitionIdle is robust enough to
+    // get the correct end state.
+    @FlakyTest(bugId = 246490534)
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index ef532e4..5779425 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,6 +64,8 @@
     private ShellExecutor mTestExecutor;
     @Mock
     private Handler mMockHandler;
+    @Mock
+    private Transitions mMockTransitions;
 
     private DesktopModeController mController;
     private ShellInit mShellInit;
@@ -72,7 +75,7 @@
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
 
         mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
-                mRootDisplayAreaOrganizer, mMockHandler);
+                mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions);
 
         mShellInit.init();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
new file mode 100644
index 0000000..9b28d11
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.desktopmode
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeTaskRepositoryTest : ShellTestCase() {
+
+    private lateinit var repo: DesktopModeTaskRepository
+
+    @Before
+    fun setUp() {
+        repo = DesktopModeTaskRepository()
+    }
+
+    @Test
+    fun addActiveTask_listenerNotifiedAndTaskIsActive() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+        assertThat(repo.isActiveTask(1)).isTrue()
+    }
+
+    @Test
+    fun addActiveTask_sameTaskDoesNotNotify() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        repo.addActiveTask(1)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+    }
+
+    @Test
+    fun addActiveTask_multipleTasksAddedNotifiesForEach() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        repo.addActiveTask(2)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+    }
+
+    @Test
+    fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        repo.removeActiveTask(1)
+        // Notify once for add and once for remove
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+        assertThat(repo.isActiveTask(1)).isFalse()
+    }
+
+    @Test
+    fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
+        val listener = TestListener()
+        repo.addListener(listener)
+        repo.removeActiveTask(99)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
+    }
+
+    @Test
+    fun isActiveTask_notExistingTaskReturnsFalse() {
+        assertThat(repo.isActiveTask(99)).isFalse()
+    }
+
+    class TestListener : DesktopModeTaskRepository.Listener {
+        var activeTaskChangedCalls = 0
+        override fun onActiveTasksChanged() {
+            activeTaskChangedCalls++
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e9a1e25..cadfeb0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -82,6 +83,8 @@
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
     private ShellCommandHandler mShellCommandHandler;
+    @Mock
+    private DesktopModeTaskRepository mDesktopModeTaskRepository;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
@@ -94,7 +97,8 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
-                mShellCommandHandler, mTaskStackListener, mMainExecutor));
+                mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository),
+                mMainExecutor));
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
                 null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
                 mMainExecutor);
@@ -195,8 +199,8 @@
         ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
-        mRecentTasksController.addActiveFreeformTask(1);
-        mRecentTasksController.addActiveFreeformTask(3);
+        when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
diff --git a/location/java/android/location/LocationTime.java b/location/java/android/location/LocationTime.java
index e5535d1..2f03508 100644
--- a/location/java/android/location/LocationTime.java
+++ b/location/java/android/location/LocationTime.java
@@ -20,28 +20,32 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.time.Duration;
+import java.time.Instant;
+
 /**
- * Data class for passing location derived time.
+ * Data class for passing GNSS-derived time.
  * @hide
  */
 public final class LocationTime implements Parcelable {
 
-    private final long mTime;
+    private final long mUnixEpochTimeMillis;
     private final long mElapsedRealtimeNanos;
 
-    public LocationTime(long time, long elapsedRealtimeNanos) {
-        mTime = time;
+    public LocationTime(long unixEpochTimeMillis, long elapsedRealtimeNanos) {
+        mUnixEpochTimeMillis = unixEpochTimeMillis;
         mElapsedRealtimeNanos = elapsedRealtimeNanos;
     }
 
     /**
-     * The current time, according to the Gnss location provider. */
-    public long getTime() {
-        return mTime;
+     * The Unix epoch time in millis, according to the Gnss location provider.
+     */
+    public long getUnixEpochTimeMillis() {
+        return mUnixEpochTimeMillis;
     }
 
     /**
-     * The elapsed nanos since boot {@link #getTime} was computed at.
+     * The elapsed nanos since boot when {@link #getUnixEpochTimeMillis} was the current time.
      */
     public long getElapsedRealtimeNanos() {
         return mElapsedRealtimeNanos;
@@ -49,7 +53,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeLong(mTime);
+        out.writeLong(mUnixEpochTimeMillis);
         out.writeLong(mElapsedRealtimeNanos);
     }
 
@@ -58,8 +62,18 @@
         return 0;
     }
 
+    @Override
+    public String toString() {
+        return "LocationTime{"
+                + "mUnixEpochTimeMillis=" + Instant.ofEpochMilli(mUnixEpochTimeMillis)
+                + "(" + mUnixEpochTimeMillis + ")"
+                + ", mElapsedRealtimeNanos=" + Duration.ofNanos(mElapsedRealtimeNanos)
+                + "(" + mElapsedRealtimeNanos + ")"
+                + '}';
+    }
+
     public static final @NonNull Parcelable.Creator<LocationTime> CREATOR =
-            new Parcelable.Creator<LocationTime>() {
+            new Parcelable.Creator<>() {
                 public LocationTime createFromParcel(Parcel in) {
                     long time = in.readLong();
                     long elapsedRealtimeNanos = in.readLong();
diff --git a/packages/CredentialManager/OWNERS b/packages/CredentialManager/OWNERS
new file mode 100644
index 0000000..f3b43c1
--- /dev/null
+++ b/packages/CredentialManager/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/credentials/OWNERS
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 5dc2aa0..f8667ed 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,7 +16,7 @@
 
 buildscript {
     ext {
-        spa_min_sdk = 31
+        spa_min_sdk = 21
         jetpack_compose_version = '1.2.0-alpha04'
         jetpack_compose_material3_version = '1.0.0-alpha06'
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
index 787d096..783fef7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
@@ -19,6 +19,7 @@
 import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -30,6 +31,7 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 
 object SpaEnvironment {
@@ -49,6 +51,8 @@
                 SettingsPagerPageProvider,
                 FooterPageProvider,
                 IllustrationPageProvider,
+                CategoryPageProvider,
+                ActionButtonPageProvider,
             ),
             rootPages = listOf(
                 SettingsPage.create(HomePageProvider.name)
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
new file mode 100644
index 0000000..96e2498
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.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.spa.gallery.button
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Launch
+import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+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.button.ActionButton
+import com.android.settingslib.spa.widget.button.ActionButtons
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val TITLE = "Sample ActionButton"
+
+object ActionButtonPageProvider : SettingsPageProvider {
+    override val name = "ActionButton"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        ActionButtonPage()
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+            .setIsAllowSearch(true)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+}
+
+@Composable
+fun ActionButtonPage() {
+    RegularScaffold(title = TITLE) {
+        val actionButtons = listOf(
+            ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+            ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
+            ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
+        )
+        ActionButtons(actionButtons)
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ActionButtonPagePreview() {
+    SettingsTheme {
+        ActionButtonPage()
+    }
+}
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 d8ef937..a16ee6c 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
@@ -25,6 +25,7 @@
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -32,6 +33,7 @@
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
+import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
 
@@ -48,6 +50,8 @@
             SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
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
new file mode 100644
index 0000000..9a525bf
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.ui
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.tooling.preview.Preview
+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.compose.stateOf
+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.Category
+import com.android.settingslib.spa.widget.ui.CategoryTitle
+
+private const val TITLE = "Sample Category"
+
+object CategoryPageProvider : SettingsPageProvider {
+    override val name = "Spinner"
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+            .setIsAllowSearch(true)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        CategoryPage()
+    }
+}
+
+@Composable
+private fun CategoryPage() {
+    RegularScaffold(title = TITLE) {
+        CategoryTitle("Category A")
+        Preference(remember {
+            object : PreferenceModel {
+                override val title = "Preference 1"
+                override val summary = stateOf("Summary 1")
+            }
+        })
+        Preference(remember {
+            object : PreferenceModel {
+                override val title = "Preference 2"
+                override val summary = stateOf("Summary 2")
+            }
+        })
+        Category("Category B") {
+            Preference(remember {
+                object : PreferenceModel {
+                    override val title = "Preference 3"
+                    override val summary = stateOf("Summary 3")
+                }
+            })
+            Preference(remember {
+                object : PreferenceModel {
+                    override val title = "Preference 4"
+                    override val summary = stateOf("Summary 4")
+                }
+            })
+        }
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SpinnerPagePreview() {
+    SettingsTheme {
+        SpinnerPageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
index 4626f0b..3fa8c65 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
@@ -16,11 +16,15 @@
 
 package com.android.settingslib.spa.framework.theme
 
+import android.os.Build
 import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.darkColorScheme
 import androidx.compose.material3.dynamicDarkColorScheme
 import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 
 @Composable
@@ -28,8 +32,15 @@
     val context = LocalContext.current
     return remember(isDarkTheme) {
         when {
-            isDarkTheme -> dynamicDarkColorScheme(context)
-            else -> dynamicLightColorScheme(context)
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+                if (isDarkTheme) dynamicDarkColorScheme(context)
+                else dynamicLightColorScheme(context)
+            }
+            isDarkTheme -> darkColorScheme()
+            else -> lightColorScheme()
         }
     }
 }
+
+val ColorScheme.divider: Color
+    get() = onSurface.copy(SettingsOpacity.Divider)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
index bc316f7..fa17e08 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.framework.theme
 
 import android.content.Context
+import android.os.Build
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.staticCompositionLocalOf
@@ -44,8 +45,12 @@
     val context = LocalContext.current
     return remember(isDarkTheme) {
         when {
-            isDarkTheme -> dynamicDarkColorScheme(context)
-            else -> dynamicLightColorScheme(context)
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+                if (isDarkTheme) dynamicDarkColorScheme(context)
+                else dynamicLightColorScheme(context)
+            }
+            isDarkTheme -> darkColorScheme()
+            else -> lightColorScheme()
         }
     }
 }
@@ -101,3 +106,37 @@
         onSpinnerItemContainer = tonalPalette.neutralVariant30,
     )
 }
+
+private fun darkColorScheme(): SettingsColorScheme {
+    val tonalPalette = tonalPalette()
+    return SettingsColorScheme(
+        background = tonalPalette.neutral10,
+        categoryTitle = tonalPalette.primary90,
+        surface = tonalPalette.neutral20,
+        surfaceHeader = tonalPalette.neutral30,
+        secondaryText = tonalPalette.neutralVariant80,
+        primaryContainer = tonalPalette.secondary90,
+        onPrimaryContainer = tonalPalette.neutral10,
+        spinnerHeaderContainer = tonalPalette.primary90,
+        onSpinnerHeaderContainer = tonalPalette.neutral10,
+        spinnerItemContainer = tonalPalette.secondary90,
+        onSpinnerItemContainer = tonalPalette.neutralVariant30,
+    )
+}
+
+private fun lightColorScheme(): SettingsColorScheme {
+    val tonalPalette = tonalPalette()
+    return SettingsColorScheme(
+        background = tonalPalette.neutral95,
+        categoryTitle = tonalPalette.primary40,
+        surface = tonalPalette.neutral99,
+        surfaceHeader = tonalPalette.neutral90,
+        secondaryText = tonalPalette.neutralVariant30,
+        primaryContainer = tonalPalette.primary90,
+        onPrimaryContainer = tonalPalette.neutral10,
+        spinnerHeaderContainer = tonalPalette.primary90,
+        onSpinnerHeaderContainer = tonalPalette.neutral10,
+        spinnerItemContainer = tonalPalette.secondary90,
+        onSpinnerItemContainer = tonalPalette.neutralVariant30,
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
index f81f5e7..c205aae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
@@ -118,6 +118,84 @@
     val tertiary0: Color,
 )
 
+/** Static colors in Material. */
+internal fun tonalPalette() = SettingsTonalPalette(
+    // The neutral static tonal range.
+    neutral100 = Color(red = 255, green = 255, blue = 255),
+    neutral99 = Color(red = 255, green = 251, blue = 254),
+    neutral95 = Color(red = 244, green = 239, blue = 244),
+    neutral90 = Color(red = 230, green = 225, blue = 229),
+    neutral80 = Color(red = 201, green = 197, blue = 202),
+    neutral70 = Color(red = 174, green = 170, blue = 174),
+    neutral60 = Color(red = 147, green = 144, blue = 148),
+    neutral50 = Color(red = 120, green = 117, blue = 121),
+    neutral40 = Color(red = 96, green = 93, blue = 98),
+    neutral30 = Color(red = 72, green = 70, blue = 73),
+    neutral20 = Color(red = 49, green = 48, blue = 51),
+    neutral10 = Color(red = 28, green = 27, blue = 31),
+    neutral0 = Color(red = 0, green = 0, blue = 0),
+
+    // The neutral variant static tonal range, sometimes called "neutral 2".
+    neutralVariant100 = Color(red = 255, green = 255, blue = 255),
+    neutralVariant99 = Color(red = 255, green = 251, blue = 254),
+    neutralVariant95 = Color(red = 245, green = 238, blue = 250),
+    neutralVariant90 = Color(red = 231, green = 224, blue = 236),
+    neutralVariant80 = Color(red = 202, green = 196, blue = 208),
+    neutralVariant70 = Color(red = 174, green = 169, blue = 180),
+    neutralVariant60 = Color(red = 147, green = 143, blue = 153),
+    neutralVariant50 = Color(red = 121, green = 116, blue = 126),
+    neutralVariant40 = Color(red = 96, green = 93, blue = 102),
+    neutralVariant30 = Color(red = 73, green = 69, blue = 79),
+    neutralVariant20 = Color(red = 50, green = 47, blue = 55),
+    neutralVariant10 = Color(red = 29, green = 26, blue = 34),
+    neutralVariant0 = Color(red = 0, green = 0, blue = 0),
+
+    // The primary static tonal range.
+    primary100 = Color(red = 255, green = 255, blue = 255),
+    primary99 = Color(red = 255, green = 251, blue = 254),
+    primary95 = Color(red = 246, green = 237, blue = 255),
+    primary90 = Color(red = 234, green = 221, blue = 255),
+    primary80 = Color(red = 208, green = 188, blue = 255),
+    primary70 = Color(red = 182, green = 157, blue = 248),
+    primary60 = Color(red = 154, green = 130, blue = 219),
+    primary50 = Color(red = 127, green = 103, blue = 190),
+    primary40 = Color(red = 103, green = 80, blue = 164),
+    primary30 = Color(red = 79, green = 55, blue = 139),
+    primary20 = Color(red = 56, green = 30, blue = 114),
+    primary10 = Color(red = 33, green = 0, blue = 93),
+    primary0 = Color(red = 33, green = 0, blue = 93),
+
+    // The secondary static tonal range.
+    secondary100 = Color(red = 255, green = 255, blue = 255),
+    secondary99 = Color(red = 255, green = 251, blue = 254),
+    secondary95 = Color(red = 246, green = 237, blue = 255),
+    secondary90 = Color(red = 232, green = 222, blue = 248),
+    secondary80 = Color(red = 204, green = 194, blue = 220),
+    secondary70 = Color(red = 176, green = 167, blue = 192),
+    secondary60 = Color(red = 149, green = 141, blue = 165),
+    secondary50 = Color(red = 122, green = 114, blue = 137),
+    secondary40 = Color(red = 98, green = 91, blue = 113),
+    secondary30 = Color(red = 74, green = 68, blue = 88),
+    secondary20 = Color(red = 51, green = 45, blue = 65),
+    secondary10 = Color(red = 29, green = 25, blue = 43),
+    secondary0 = Color(red = 0, green = 0, blue = 0),
+
+    // The tertiary static tonal range.
+    tertiary100 = Color(red = 255, green = 255, blue = 255),
+    tertiary99 = Color(red = 255, green = 251, blue = 250),
+    tertiary95 = Color(red = 255, green = 236, blue = 241),
+    tertiary90 = Color(red = 255, green = 216, blue = 228),
+    tertiary80 = Color(red = 239, green = 184, blue = 200),
+    tertiary70 = Color(red = 210, green = 157, blue = 172),
+    tertiary60 = Color(red = 181, green = 131, blue = 146),
+    tertiary50 = Color(red = 152, green = 105, blue = 119),
+    tertiary40 = Color(red = 125, green = 82, blue = 96),
+    tertiary30 = Color(red = 99, green = 59, blue = 72),
+    tertiary20 = Color(red = 73, green = 37, blue = 50),
+    tertiary10 = Color(red = 49, green = 17, blue = 29),
+    tertiary0 = Color(red = 0, green = 0, blue = 0),
+)
+
 /** Dynamic colors in Material. */
 internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette(
     // The neutral tonal range from the generated dynamic color palette.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
new file mode 100644
index 0000000..3555ec7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.widget.button
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Launch
+import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+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.framework.theme.divider
+
+data class ActionButton(
+    val text: String,
+    val imageVector: ImageVector,
+    val onClick: () -> Unit,
+)
+
+@Composable
+fun ActionButtons(actionButtons: List<ActionButton>) {
+    Row(
+        Modifier
+            .padding(SettingsDimension.itemPaddingVertical)
+            .clip(SettingsShape.CornerLarge)
+            .height(IntrinsicSize.Min)
+    ) {
+        for ((index, actionButton) in actionButtons.withIndex()) {
+            if (index > 0) ButtonDivider()
+            ActionButton(actionButton)
+        }
+    }
+}
+
+@Composable
+private fun RowScope.ActionButton(actionButton: ActionButton) {
+    FilledTonalButton(
+        onClick = actionButton.onClick,
+        modifier = Modifier
+            .weight(1f)
+            .fillMaxHeight(),
+        shape = RectangleShape,
+        colors = ButtonDefaults.filledTonalButtonColors(
+            containerColor = MaterialTheme.colorScheme.surface,
+            contentColor = SettingsTheme.colorScheme.categoryTitle,
+        ),
+        contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp),
+    ) {
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Icon(
+                imageVector = actionButton.imageVector,
+                contentDescription = null,
+                modifier = Modifier.size(SettingsDimension.itemIconSize),
+            )
+            Spacer(Modifier.height(4.dp))
+            Text(
+                text = actionButton.text,
+                style = MaterialTheme.typography.labelLarge,
+            )
+        }
+    }
+}
+
+@Composable
+private fun ButtonDivider() {
+    Box(
+        Modifier
+            .width(1.dp)
+            .background(color = MaterialTheme.colorScheme.divider)
+    )
+}
+
+@Preview
+@Composable
+private fun ActionButtonsPreview() {
+    SettingsTheme {
+        ActionButtons(
+            listOf(
+                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
+                ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
+            )
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 77ea3c5..5663610 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -30,7 +30,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.divider
 
 @Composable
 internal fun TwoTargetPreference(
@@ -67,6 +67,6 @@
         Modifier
             .padding(horizontal = SettingsDimension.itemPaddingEnd)
             .size(width = 1.dp, height = SettingsDimension.itemDividerHeight)
-            .background(color = MaterialTheme.colorScheme.onSurface.copy(SettingsOpacity.Divider))
+            .background(color = MaterialTheme.colorScheme.divider)
     )
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
new file mode 100644
index 0000000..6aac5bf3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.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 com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+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.layout.onGloballyPositioned
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+/**
+ * A category title that is placed before a group of similar items.
+ */
+@Composable
+fun CategoryTitle(title: String) {
+    Text(
+        text = title,
+        modifier = Modifier.padding(
+            start = SettingsDimension.itemPaddingStart,
+            top = 20.dp,
+            end = SettingsDimension.itemPaddingEnd,
+            bottom = 8.dp,
+        ),
+        color = SettingsTheme.colorScheme.categoryTitle,
+        style = MaterialTheme.typography.labelMedium,
+    )
+}
+
+/**
+ * A container that is used to group similar items. A [Category] displays a [CategoryTitle] and
+ * visually separates groups of items.
+ */
+@Composable
+fun Category(title: String, content: @Composable ColumnScope.() -> Unit) {
+    Column {
+        var displayTitle by remember { mutableStateOf(false) }
+        if (displayTitle) CategoryTitle(title = title)
+        Column(
+            modifier = Modifier.onGloballyPositioned { coordinates ->
+                displayTitle = coordinates.size.height > 0
+            },
+            content = content,
+        )
+    }
+}
+
+@Preview
+@Composable
+private fun CategoryPreview() {
+    SettingsTheme {
+        CategoryTitle("Appearance")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
new file mode 100644
index 0000000..8101a94
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -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.settingslib.spa.widget.button
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Launch
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ActionButtonsTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun button_displayed() {
+        composeTestRule.setContent {
+            ActionButtons(
+                listOf(
+                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                )
+            )
+        }
+
+        composeTestRule.onNodeWithText("Open").assertIsDisplayed()
+    }
+
+    @Test
+    fun button_clickable() {
+        var clicked by mutableStateOf(false)
+        composeTestRule.setContent {
+            ActionButtons(
+                listOf(
+                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {
+                        clicked = true
+                    },
+                )
+            )
+        }
+
+        composeTestRule.onNodeWithText("Open").performClick()
+
+        assertThat(clicked).isTrue()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
new file mode 100644
index 0000000..16e09ee
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.widget.ui
+
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CategoryTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun categoryTitle() {
+        composeTestRule.setContent {
+            CategoryTitle(title = "CategoryTitle")
+        }
+
+        composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
+    }
+
+    @Test
+    fun category_hasContent_titleDisplayed() {
+        composeTestRule.setContent {
+            Category(title = "CategoryTitle") {
+                Preference(remember {
+                    object : PreferenceModel {
+                        override val title = "Some Preference"
+                        override val summary = stateOf("Some summary")
+                    }
+                })
+            }
+        }
+
+        composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
+    }
+
+    @Test
+    fun category_noContent_titleNotDisplayed() {
+        composeTestRule.setContent {
+            Category(title = "CategoryTitle") {}
+        }
+
+        composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist()
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml
index b2302a5..25dbe00 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml
@@ -14,7 +14,7 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- [CHAR LIMIT=25] Text shown when there are no applications to display. -->
     <string name="no_applications">No apps.</string>
     <!-- [CHAR LIMIT=NONE] Menu for manage apps to control whether system processes are shown -->
@@ -25,4 +25,6 @@
     <string name="app_permission_summary_allowed">Allowed</string>
     <!-- Preference summary text for an app when it is disallowed for a permission. [CHAR LIMIT=45] -->
     <string name="app_permission_summary_not_allowed">Not allowed</string>
+    <!-- Manage applications, version string displayed in app snippet -->
+    <string name="version_text">version <xliff:g id="version_num">%1$s</xliff:g></string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
index 6e1afd9..99a08ab 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
@@ -22,4 +22,7 @@
 val ApplicationInfo.userId: Int
     get() = UserHandle.getUserId(uid)
 
+val ApplicationInfo.userHandle: UserHandle
+    get() = UserHandle.getUserHandleForUid(uid)
+
 fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index f51d2db..c89ffe5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -17,6 +17,8 @@
 package com.android.settingslib.spaprivileged.template.app
 
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.text.BidiFormatter
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -25,49 +27,60 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Divider
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.SettingsBody
 import com.android.settingslib.spa.widget.ui.SettingsTitle
-import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.rememberAppRepository
 
-@Composable
-fun AppInfo(packageName: String, userId: Int) {
-    Column(
-        modifier = Modifier
-            .fillMaxWidth()
-            .padding(
-                horizontal = SettingsDimension.itemPaddingStart,
-                vertical = SettingsDimension.itemPaddingVertical,
-            ),
-        horizontalAlignment = Alignment.CenterHorizontally,
-    ) {
-        val packageInfo =
-            remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } ?: return
-        Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
-            AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
+class AppInfoProvider(private val packageInfo: PackageInfo) {
+    @Composable
+    fun AppInfo(displayVersion: Boolean = false) {
+        Column(
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(
+                    horizontal = SettingsDimension.itemPaddingStart,
+                    vertical = SettingsDimension.itemPaddingVertical,
+                ),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+                AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
+            }
+            AppLabel(packageInfo.applicationInfo)
+            if (displayVersion) AppVersion()
         }
-        AppLabel(packageInfo.applicationInfo)
-        AppVersion(packageInfo.versionName)
+    }
+
+    @Composable
+    private fun AppVersion() {
+        if (packageInfo.versionName == null) return
+        Spacer(modifier = Modifier.height(4.dp))
+        SettingsBody(packageInfo.versionName)
+    }
+
+    @Composable
+    fun FooterAppVersion() {
+        if (packageInfo.versionName == null) return
+        Divider()
+        Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+            val versionName = BidiFormatter.getInstance().unicodeWrap(packageInfo.versionName)
+            SettingsBody(stringResource(R.string.version_text, versionName))
+        }
     }
 }
 
 @Composable
-private fun AppVersion(versionName: String?) {
-    if (versionName == null) return
-    Spacer(modifier = Modifier.height(4.dp))
-    SettingsBody(versionName)
-}
-
-@Composable
-fun AppIcon(app: ApplicationInfo, size: Dp) {
+internal fun AppIcon(app: ApplicationInfo, size: Dp) {
     val appRepository = rememberAppRepository()
     Image(
         painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
@@ -77,7 +90,7 @@
 }
 
 @Composable
-fun AppLabel(app: ApplicationInfo) {
+internal fun AppLabel(app: ApplicationInfo) {
     val appRepository = rememberAppRepository()
     SettingsTitle(appRepository.produceLabel(app))
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 9b45318..4f88398 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -17,8 +17,10 @@
 package com.android.settingslib.spaprivileged.template.app
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Footer
+import com.android.settingslib.spaprivileged.model.app.PackageManagers
 
 @Composable
 fun AppInfoPage(
@@ -29,7 +31,12 @@
     content: @Composable () -> Unit,
 ) {
     RegularScaffold(title = title) {
-        AppInfo(packageName, userId)
+        val appInfoProvider = remember {
+            val packageInfo = PackageManagers.getPackageInfoAsUser(packageName, userId)
+                ?: return@RegularScaffold
+            AppInfoProvider(packageInfo)
+        }
+        appInfoProvider.AppInfo(displayVersion = true)
 
         content()
 
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 ae4f8bd..80b2364 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
@@ -34,6 +34,8 @@
 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.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
@@ -80,10 +82,31 @@
 
     companion object {
         @Composable
-        internal fun navigator(permissionType: String, app: ApplicationInfo) =
+        fun navigator(permissionType: String, app: ApplicationInfo) =
             navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
 
-        internal fun buildPageData(permissionType: String): SettingsPage {
+        @Composable
+        fun <T : AppRecord> EntryItem(
+            permissionType: String,
+            app: ApplicationInfo,
+            listModel: TogglePermissionAppListModel<T>,
+        ) {
+            val context = LocalContext.current
+            val internalListModel = remember {
+                TogglePermissionInternalAppListModel(context, listModel)
+            }
+            val record = remember { listModel.transformItem(app) }
+            if (!remember { listModel.isChangeable(record) }) return
+            Preference(
+                object : PreferenceModel {
+                    override val title = stringResource(listModel.pageTitleResId)
+                    override val summary = internalListModel.getSummary(record)
+                    override val onClick = navigator(permissionType, app)
+                }
+            )
+        }
+
+        fun buildPageData(permissionType: String): SettingsPage {
             return SettingsPage.create(
                 PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
         }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 74a50de..5d49d24 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -20,13 +20,10 @@
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.ui.res.stringResource
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spa.framework.util.asyncMapItem
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import kotlinx.coroutines.flow.Flow
 
@@ -85,28 +82,22 @@
 
     fun createModel(context: Context): TogglePermissionAppListModel<out AppRecord>
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return TogglePermissionAppListPageProvider.buildInjectEntry(permissionType)
-    }
-
-    @Composable
-    fun EntryItem() {
-        val listModel = rememberContext(::createModel)
-        Preference(
-            object : PreferenceModel {
-                override val title = stringResource(listModel.pageTitleResId)
-                override val onClick = TogglePermissionAppListPageProvider.navigator(permissionType)
-            }
-        )
-    }
+    fun buildAppListInjectEntry(): SettingsEntryBuilder =
+        TogglePermissionAppListPageProvider.buildInjectEntry(permissionType) { createModel(it) }
 
     /**
      * Gets the route to the toggle permission App List page.
      *
      * Expose route to enable enter from non-SPA pages.
      */
-    fun getRoute(): String =
+    fun getAppListRoute(): String =
         TogglePermissionAppListPageProvider.getRoute(permissionType)
+
+    @Composable
+    fun InfoPageEntryItem(app: ApplicationInfo) {
+        val listModel = rememberContext(::createModel)
+        TogglePermissionAppInfoPageProvider.EntryItem(permissionType, app, listModel)
+    }
 }
 
 class TogglePermissionAppListTemplate(
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 475e930..bd60bd36 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
@@ -34,7 +34,10 @@
 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.compose.rememberContext
 import com.android.settingslib.spa.framework.util.getStringArg
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
@@ -107,20 +110,29 @@
          *
          * Expose route to enable enter from non-SPA pages.
          */
-        internal fun getRoute(permissionType: String) = "$PAGE_NAME/$permissionType"
+        fun getRoute(permissionType: String) = "$PAGE_NAME/$permissionType"
 
-        @Composable
-        internal fun navigator(permissionType: String) = navigator(route = getRoute(permissionType))
-
-        internal fun buildInjectEntry(permissionType: String): SettingsEntryBuilder {
+        fun buildInjectEntry(
+            permissionType: String,
+            listModelSupplier: (Context) -> TogglePermissionAppListModel<out AppRecord>,
+        ): SettingsEntryBuilder {
             val appListPage = SettingsPage.create(
                 PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
             return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
+                .setUiLayoutFn {
+                    val listModel = rememberContext(listModelSupplier)
+                    Preference(
+                        object : PreferenceModel {
+                            override val title = stringResource(listModel.pageTitleResId)
+                            override val onClick = navigator(route = getRoute(permissionType))
+                        }
+                    )
+                }
         }
     }
 }
 
-private class TogglePermissionInternalAppListModel<T : AppRecord>(
+internal class TogglePermissionInternalAppListModel<T : AppRecord>(
     private val context: Context,
     private val listModel: TogglePermissionAppListModel<T>,
 ) : AppListModel<T> {
@@ -132,6 +144,11 @@
 
     @Composable
     override fun getSummary(option: Int, record: T): State<String> {
+        return getSummary(record)
+    }
+
+    @Composable
+    fun getSummary(record: T): State<String> {
         val restrictionsProvider = remember {
             val restrictions = Restrictions(
                 userId = record.app.userId,
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 45746d9..cfff519 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -19,11 +19,11 @@
     name: "SettingsLib-annotation-processor",
     processor_class: "com.android.settingslib.search.IndexableProcessor",
     static_libs: [
-        "javapoet-prebuilt-jar",
+        "javapoet",
     ],
     srcs: [
         "processor-src/**/*.java",
-        "src/com/android/settingslib/search/SearchIndexable.java"
+        "src/com/android/settingslib/search/SearchIndexable.java",
     ],
     java_resource_dirs: ["resources"],
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index 3152e65..fa056e2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -16,6 +16,22 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
+import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING;
+import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
+import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING;
+import static android.bluetooth.BluetoothAdapter.STATE_OFF;
+import static android.bluetooth.BluetoothAdapter.STATE_ON;
+import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
+import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * BluetoothCallback provides a callback interface for the settings
@@ -33,7 +49,7 @@
      * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
      * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}.
      */
-    default void onBluetoothStateChanged(int bluetoothState) {}
+    default void onBluetoothStateChanged(@AdapterState int bluetoothState) {}
 
     /**
      * It will be called when the local Bluetooth adapter has started
@@ -54,14 +70,14 @@
      *
      * @param cachedDevice the Bluetooth device.
      */
-    default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {}
+    default void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {}
 
     /**
      * It will be called when requiring to remove a remote device from CachedBluetoothDevice list
      *
      * @param cachedDevice the Bluetooth device.
      */
-    default void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {}
+    default void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {}
 
     /**
      * It will be called when bond state of a remote device is changed.
@@ -73,7 +89,8 @@
      * {@link android.bluetooth.BluetoothDevice#BOND_BONDING},
      * {@link android.bluetooth.BluetoothDevice#BOND_BONDED}.
      */
-    default void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {}
+    default void onDeviceBondStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice, int bondState) {}
 
     /**
      * It will be called in following situations:
@@ -89,7 +106,9 @@
      * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED},
      * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}.
      */
-    default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {}
+    default void onConnectionStateChanged(
+            @Nullable CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state) {}
 
     /**
      * It will be called when device been set as active for {@code bluetoothProfile}
@@ -101,7 +120,8 @@
      * @param activeDevice the active Bluetooth device.
      * @param bluetoothProfile the profile of active Bluetooth device.
      */
-    default void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {}
+    default void onActiveDeviceChanged(
+            @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {}
 
     /**
      * It will be called in following situations:
@@ -124,8 +144,10 @@
      * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}.
      * @param bluetoothProfile the BluetoothProfile id.
      */
-    default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
-            int state, int bluetoothProfile) {
+    default void onProfileConnectionStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state,
+            int bluetoothProfile) {
     }
 
     /**
@@ -138,6 +160,24 @@
      *                     {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED},
      *                     {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}
      */
-    default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-    }
+    default void onAclConnectionStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice, int state) {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_DISCONNECTED,
+            STATE_CONNECTING,
+            STATE_CONNECTED,
+            STATE_DISCONNECTING,
+    })
+    @interface ConnectionState {}
+
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_OFF,
+            STATE_TURNING_ON,
+            STATE_ON,
+            STATE_TURNING_OFF,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AdapterState {}
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 51812f0..a9f4e9c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -32,6 +32,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -193,19 +194,19 @@
         return deviceAdded;
     }
 
-    void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+    void dispatchDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {
         for (BluetoothCallback callback : mCallbacks) {
             callback.onDeviceAdded(cachedDevice);
         }
     }
 
-    void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
+    void dispatchDeviceRemoved(@NonNull CachedBluetoothDevice cachedDevice) {
         for (BluetoothCallback callback : mCallbacks) {
             callback.onDeviceDeleted(cachedDevice);
         }
     }
 
-    void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state,
+    void dispatchProfileConnectionStateChanged(@NonNull CachedBluetoothDevice device, int state,
             int bluetoothProfile) {
         for (BluetoothCallback callback : mCallbacks) {
             callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
@@ -228,7 +229,8 @@
     }
 
     @VisibleForTesting
-    void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
+    void dispatchActiveDeviceChanged(
+            @Nullable CachedBluetoothDevice activeDevice,
             int bluetoothProfile) {
         for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) {
             boolean isActive = Objects.equals(cachedDevice, activeDevice);
@@ -239,7 +241,7 @@
         }
     }
 
-    private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state) {
+    private void dispatchAclStateChanged(@NonNull CachedBluetoothDevice activeDevice, int state) {
         for (BluetoothCallback callback : mCallbacks) {
             callback.onAclConnectionStateChanged(activeDevice, state);
         }
@@ -456,6 +458,7 @@
                 Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
                 return;
             }
+            @Nullable
             CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
             int bluetoothProfile = 0;
             if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index d84e57a..3e33da5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -49,6 +49,10 @@
     }
 
     @Override
+    public void clicked(int sourceCategory, String key) {
+    }
+
+    @Override
     public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
         final LogMaker logMaker = new LogMaker(category)
                 .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index d4ef3d7..cceca13 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -34,6 +34,11 @@
     void hidden(Context context, int category, int visibleTime);
 
     /**
+     * Logs a click event when user click item.
+     */
+    void clicked(int category, String key);
+
+    /**
      * Logs an user action.
      */
     void action(Context context, int category, Pair<Integer, Object>... taggedData);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 7390b6a..915421a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -96,6 +96,18 @@
     }
 
     /**
+     * Logs an event when user click item.
+     *
+     * @param category the target page id
+     * @param key the key id that user clicked
+     */
+    public void clicked(int category, String key) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.clicked(category, key);
+        }
+    }
+
+    /**
      * Logs a simple action without page id or attribution
      *
      * @param category the target page
@@ -138,7 +150,7 @@
     }
 
     public int getMetricsCategory(Object object) {
-        if (object == null || !(object instanceof Instrumentable)) {
+        if (!(object instanceof Instrumentable)) {
             return MetricsEvent.VIEW_UNKNOWN;
         }
         return ((Instrumentable) object).getMetricsCategory();
@@ -198,11 +210,7 @@
             // Not loggable
             return false;
         }
-        action(sourceMetricsCategory,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                logKey,
-                0);
+        clicked(sourceMetricsCategory, logKey);
         return true;
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 0e2a3cb..3352d86 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -80,12 +80,7 @@
                 MetricsEvent.SETTINGS_GESTURES);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                key,
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, key);
     }
 
     @Test
@@ -98,12 +93,7 @@
                 MetricsEvent.SETTINGS_GESTURES);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                Intent.ACTION_ASSIST,
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, Intent.ACTION_ASSIST);
     }
 
     @Test
@@ -116,12 +106,7 @@
                 MetricsEvent.SETTINGS_GESTURES);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                fragment,
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, fragment);
     }
 
     @Test
@@ -140,12 +125,7 @@
         final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                Intent.ACTION_ASSIST,
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, Intent.ACTION_ASSIST);
     }
 
     @Test
@@ -155,12 +135,7 @@
         final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                "pkg/cls",
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls");
     }
 
     @Test
@@ -171,12 +146,7 @@
                 MetricsEvent.SETTINGS_GESTURES, false);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                "pkg/cls/personal",
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls/personal");
     }
 
     @Test
@@ -187,12 +157,7 @@
                 MetricsEvent.SETTINGS_GESTURES, true);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                "pkg/cls/work",
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls/work");
     }
 
     @Test
@@ -226,12 +191,7 @@
                 MetricsEvent.SETTINGS_GESTURES);
 
         assertThat(loggable).isTrue();
-        verify(mLogWriter).action(
-                MetricsEvent.SETTINGS_GESTURES,
-                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                SettingsEnums.PAGE_UNKNOWN,
-                key,
-                0);
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, key);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 10ac5d9..f501682 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -72,6 +72,7 @@
         Settings.System.SIP_CALL_OPTIONS,
         Settings.System.SIP_RECEIVE_CALLS,
         Settings.System.POINTER_SPEED,
+        Settings.System.VIBRATE_ON,
         Settings.System.VIBRATE_WHEN_RINGING,
         Settings.System.RINGTONE,
         Settings.System.LOCK_TO_APP_ENABLED,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index e7c7ac0..f05f637 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -83,7 +83,6 @@
                     Settings.System.SYSTEM_LOCALES, // bug?
                     Settings.System.USER_ROTATION, // backup candidate?
                     Settings.System.VIBRATE_IN_SILENT, // deprecated?
-                    Settings.System.VIBRATE_ON, // candidate for backup?
                     Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will
                                                           // not change volume
                     Settings.System.VOLUME_ALARM, // deprecated since API 2?
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index bfbccb8..7839ec8 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -498,7 +498,6 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 1aaf19e..c50340c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -147,10 +147,10 @@
     }
 
     /**
-     * Listener that is alerted when a double tap is required to confirm a single tap.
+     * Listener that is alerted when an additional tap is required to confirm a single tap.
      **/
     interface FalsingTapListener {
-        void onDoubleTapRequired();
+        void onAdditionalTapRequired();
     }
 
     /** Passed to {@link FalsingManager#onProximityEvent}. */
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml
new file mode 100644
index 0000000..a347123
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml
@@ -0,0 +1,109 @@
+<?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
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c "
+                    android:valueTo="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="33"
+                    android:propertyName="pathData"
+                    android:startOffset="17"
+                    android:valueFrom="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c "
+                    android:valueTo="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="50"
+                    android:valueFrom="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c "
+                    android:valueTo="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="83"
+                    android:propertyName="pathData"
+                    android:startOffset="117"
+                    android:valueFrom="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c "
+                    android:valueTo="M0 -8.18 C-1.81,-7.06 -3.89,-5.21 -5.95,-2.81 C-7.56,-0.94 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.99 0,7.99 C0,6.34 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-6 0,-8.18c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="433"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#edf2eb"
+                        android:fillType="nonZero"
+                        android:pathData=" M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19  M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml
new file mode 100644
index 0000000..c5609d8
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml
@@ -0,0 +1,109 @@
+<?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
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="100"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c "
+                    android:valueTo="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="100"
+                    android:valueFrom="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c "
+                    android:valueTo="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="167"
+                    android:valueFrom="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c "
+                    android:valueTo="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="100"
+                    android:propertyName="pathData"
+                    android:startOffset="233"
+                    android:valueFrom="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c "
+                    android:valueTo="M5.63 -2.97 C3.65,-4.93 0.75,-8.37 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 4.13,7.94 6.31,3.44 C6.68,2.66 7,1.37 6.87,0.06 C6.77,-0.84 6.13,-2.47 5.63,-2.97c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="433"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#edf2eb"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19  M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
index f239a8d..420ae70 100644
--- a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
+++ b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
@@ -15,11 +15,45 @@
   -->
 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#ffffff"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#ffffff"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
     <target android:name="time_group">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
                 <objectAnimator
-                    android:duration="183"
+                    android:duration="333"
                     android:propertyName="translateX"
                     android:startOffset="0"
                     android:valueFrom="0"
@@ -38,13 +72,29 @@
                 <group
                     android:name="_R_G_L_0_G"
                     android:translateX="12"
-                    android:translateY="12">
-                    <path
-                        android:name="_R_G_L_0_G_D_0_P_0"
-                        android:fillAlpha="1"
-                        android:fillColor="#ffffff"
-                        android:fillType="nonZero"
-                        android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c  M1.5 1.5 C1.5,1.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5c " />
+                    android:translateY="12.469">
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
+                        android:scaleX="1"
+                        android:scaleY="1">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:fillAlpha="1"
+                            android:fillColor="#000000"
+                            android:fillType="nonZero"
+                            android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"
+                        android:scaleX="1"
+                        android:scaleY="1">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_1"
+                            android:fillAlpha="1"
+                            android:fillColor="#000000"
+                            android:fillType="nonZero"
+                            android:pathData=" M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " />
+                    </group>
                 </group>
             </group>
             <group android:name="time_group" />
diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
index d46fafa..57d7902 100644
--- a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
+++ b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
@@ -15,35 +15,213 @@
   -->
 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:aapt="http://schemas.android.com/aapt">
-    <target android:name="_R_G_L_1_G">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
                 <objectAnimator
-                    android:duration="667"
-                    android:pathData="M 12.125,34.75C 12.104,30.958 12.021,15.792 12,12"
-                    android:propertyName="translateXY"
-                    android:propertyXName="translateX"
-                    android:propertyYName="translateY"
-                    android:startOffset="0">
+                    android:duration="250"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#ffffff"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_0_G">
+    <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
                 <objectAnimator
-                    android:duration="517"
-                    android:pathData="M 12,12C 12.021,8.312 12.104,-6.436999999999999 12.125,-10.125"
-                    android:propertyName="translateXY"
-                    android:propertyXName="translateX"
-                    android:propertyYName="translateY"
-                    android:startOffset="0">
+                    android:duration="383"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="1.1500000000000001"
+                    android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="383"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="1.1500000000000001"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="767"
+                    android:propertyName="scaleX"
+                    android:startOffset="383"
+                    android:valueFrom="1.1500000000000001"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="767"
+                    android:propertyName="scaleY"
+                    android:startOffset="383"
+                    android:valueFrom="1.1500000000000001"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
+                    android:valueTo="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="217"
+                    android:propertyName="pathData"
+                    android:startOffset="200"
+                    android:valueFrom="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c "
+                    android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="217"
+                    android:propertyName="pathData"
+                    android:startOffset="417"
+                    android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
+                    android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#ffffff"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="383"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="1.1500000000000001"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="383"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="1.1500000000000001"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="767"
+                    android:propertyName="scaleX"
+                    android:startOffset="383"
+                    android:valueFrom="1.1500000000000001"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="767"
+                    android:propertyName="scaleY"
+                    android:startOffset="383"
+                    android:valueFrom="1.1500000000000001"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c "
+                    android:valueTo="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="217"
+                    android:propertyName="pathData"
+                    android:startOffset="200"
+                    android:valueFrom="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c "
+                    android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="217"
+                    android:propertyName="pathData"
+                    android:startOffset="417"
+                    android:valueFrom="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c "
+                    android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -53,7 +231,7 @@
         <aapt:attr name="android:animation">
             <set android:ordering="together">
                 <objectAnimator
-                    android:duration="683"
+                    android:duration="1167"
                     android:propertyName="translateX"
                     android:startOffset="0"
                     android:valueFrom="0"
@@ -70,26 +248,31 @@
             android:viewportWidth="24">
             <group android:name="_R_G">
                 <group
-                    android:name="_R_G_L_1_G"
-                    android:translateX="12.125"
-                    android:translateY="34.75">
-                    <path
-                        android:name="_R_G_L_1_G_D_0_P_0"
-                        android:fillAlpha="1"
-                        android:fillColor="#ffffff"
-                        android:fillType="nonZero"
-                        android:pathData=" M10 4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.5 1.5,-8.5 C1.5,-9.33 0.83,-10 0,-10 C-0.83,-10 -1.5,-9.33 -1.5,-8.5 C-1.5,-8.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4c " />
-                </group>
-                <group
                     android:name="_R_G_L_0_G"
                     android:translateX="12"
-                    android:translateY="12">
-                    <path
-                        android:name="_R_G_L_0_G_D_0_P_0"
-                        android:fillAlpha="1"
-                        android:fillColor="#ffffff"
-                        android:fillType="nonZero"
-                        android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c  M1.5 1.5 C1.5,1.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5c " />
+                    android:translateY="12.469">
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
+                        android:scaleX="1"
+                        android:scaleY="1">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:fillAlpha="1"
+                            android:fillColor="#ffffff"
+                            android:fillType="nonZero"
+                            android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"
+                        android:scaleX="1"
+                        android:scaleY="1">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_1"
+                            android:fillAlpha="1"
+                            android:fillColor="#ffffff"
+                            android:fillType="nonZero"
+                            android:pathData=" M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c " />
+                    </group>
                 </group>
             </group>
             <group android:name="time_group" />
diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml
new file mode 100644
index 0000000..57777a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="133"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:pivotY="1"
+                    android:rotation="180"
+                    android:translateX="12"
+                    android:translateY="10.984">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:rotation="1440"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c  M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml
new file mode 100644
index 0000000..b376413
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml
@@ -0,0 +1,110 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="333"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c "
+                    android:valueTo="M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.2,0.2 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="613.191"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.086,0.422 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="rotation"
+                    android:startOffset="500"
+                    android:valueFrom="613.191"
+                    android:valueTo="720"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.334,1.012 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="933"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="12"
+                    android:translateY="10.75">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:rotation="0"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c  M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml b/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml
new file mode 100644
index 0000000..b0f85e7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml
@@ -0,0 +1,197 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="strokeColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#ffffff"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#ffffff"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c "
+                    android:valueTo="M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#ffffff"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c "
+                    android:valueTo="M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#ffffff"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c "
+                    android:valueTo="M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="517"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="-81"
+                    android:translateY="-32">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:pathData=" M93 40 C95.21,40 97,41.79 97,44 C97,46.21 95.21,48 93,48 C90.79,48 89,46.21 89,44 C89,41.79 90.79,40 93,40c "
+                        android:strokeAlpha="1"
+                        android:strokeColor="#000000"
+                        android:strokeLineCap="round"
+                        android:strokeLineJoin="round"
+                        android:strokeWidth="2" />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="-81"
+                    android:translateY="-32">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#000000"
+                        android:fillType="nonZero"
+                        android:pathData=" M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#000000"
+                        android:fillType="nonZero"
+                        android:pathData=" M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#000000"
+                        android:fillType="nonZero"
+                        android:pathData=" M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml b/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml
new file mode 100644
index 0000000..5546b6f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml
@@ -0,0 +1,197 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="strokeColor"
+                    android:startOffset="0"
+                    android:valueFrom="#ffffff"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#ffffff"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c "
+                    android:valueTo="M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#ffffff"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c "
+                    android:valueTo="M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#ffffff"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="350"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c "
+                    android:valueTo="M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="517"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="-81"
+                    android:translateY="-32">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:pathData=" M93 40 C95.21,40 97,41.79 97,44 C97,46.21 95.21,48 93,48 C90.79,48 89,46.21 89,44 C89,41.79 90.79,40 93,40c "
+                        android:strokeAlpha="1"
+                        android:strokeColor="#ffffff"
+                        android:strokeLineCap="round"
+                        android:strokeLineJoin="round"
+                        android:strokeWidth="2" />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="-81"
+                    android:translateY="-32">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_flashlight_icon_off.xml b/packages/SystemUI/res/drawable/qs_flashlight_icon_off.xml
new file mode 100644
index 0000000..157e67a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_flashlight_icon_off.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
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M0 2.02 C0.42,2.02 0.77,1.88 1.05,1.59 C1.35,1.29 1.5,0.94 1.5,0.52 C1.5,0.1 1.35,-0.25 1.05,-0.53 C0.77,-0.83 0.42,-0.98 0,-0.98 C-0.42,-0.98 -0.78,-0.83 -1.08,-0.53 C-1.36,-0.25 -1.5,0.1 -1.5,0.52 C-1.5,0.94 -1.36,1.29 -1.08,1.59 C-0.78,1.88 -0.42,2.02 0,2.02c M0 8.62 C-0.42,8.62 -2.78,8.82 -3.07,8.54 C-3.36,8.24 -3.37,-1.87 -3.37,-2.28 C-4.25,-2.69 -4.29,-5.2 -4.01,-5.48 C-3.71,-5.78 -0.42,-5.75 0,-5.75 C0.42,-5.75 4.39,-5.78 4.36,-5.36 C4.22,-2.97 4.47,-3.03 3.34,-1.78 C3.07,-1.48 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c "
+                    android:valueTo="M0 3 C0.42,3 0.77,2.86 1.05,2.58 C1.35,2.28 1.5,1.92 1.5,1.5 C1.5,1.08 1.35,0.73 1.05,0.45 C0.77,0.15 0.42,0 0,0 C-0.42,0 -0.78,0.15 -1.08,0.45 C-1.36,0.73 -1.5,1.08 -1.5,1.5 C-1.5,1.92 -1.36,2.28 -1.08,2.58 C-0.78,2.86 -0.42,3 0,3c M0 8.62 C-0.42,8.62 -2.24,8.83 -2.54,8.55 C-2.83,8.25 -2.86,9.1 -2.86,8.69 C-2.86,8.27 -3.02,8.85 -2.74,8.57 C-2.44,8.26 -0.43,8.31 -0.02,8.31 C0.4,8.31 2.4,8.17 2.68,8.47 C2.98,8.75 2.93,8.33 2.93,8.75 C2.93,9.17 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1000"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:pivotY="24"
+                    android:scaleX="0.33332999999999996"
+                    android:scaleY="0.33332999999999996"
+                    android:translateX="12"
+                    android:translateY="-4">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#1f1f1f"
+                        android:fillType="nonZero"
+                        android:pathData=" M-12 -15 C-12,-15 12,-15 12,-15 C12,-15 12,-13.8 12,-13.8 C12,-13.8 6,-4.8 6,-4.8 C6,-4.8 6,24 6,24 C6,24 -6,24 -6,24 C-6,24 -6,-4.8 -6,-4.8 C-6,-4.8 -12,-13.8 -12,-13.8 C-12,-13.8 -12,-15 -12,-15c  M12 -21 C12,-21 -12,-21 -12,-21 C-12,-21 -12,-24 -12,-24 C-12,-24 12,-24 12,-24 C12,-24 12,-21 12,-21c  M-12 -3 C-12,-3 -12,30 -12,30 C-12,30 12,30 12,30 C12,30 12,-3 12,-3 C12,-3 18,-12 18,-12 C18,-12 18,-30 18,-30 C18,-30 -18,-30 -18,-30 C-18,-30 -18,-12 -18,-12 C-18,-12 -12,-3 -12,-3c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#1f1f1f"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 2.02 C0.42,2.02 0.77,1.88 1.05,1.59 C1.35,1.29 1.5,0.94 1.5,0.52 C1.5,0.1 1.35,-0.25 1.05,-0.53 C0.77,-0.83 0.42,-0.98 0,-0.98 C-0.42,-0.98 -0.78,-0.83 -1.08,-0.53 C-1.36,-0.25 -1.5,0.1 -1.5,0.52 C-1.5,0.94 -1.36,1.29 -1.08,1.59 C-0.78,1.88 -0.42,2.02 0,2.02c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_flashlight_icon_on.xml b/packages/SystemUI/res/drawable/qs_flashlight_icon_on.xml
new file mode 100644
index 0000000..22f5e00
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_flashlight_icon_on.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
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M0 3 C0.42,3 0.77,2.86 1.05,2.58 C1.35,2.28 1.5,1.92 1.5,1.5 C1.5,1.08 1.35,0.73 1.05,0.45 C0.77,0.15 0.42,0 0,0 C-0.42,0 -0.78,0.15 -1.08,0.45 C-1.36,0.73 -1.5,1.08 -1.5,1.5 C-1.5,1.92 -1.36,2.28 -1.08,2.58 C-0.78,2.86 -0.42,3 0,3c M0 8.62 C-0.42,8.62 -2.24,8.83 -2.54,8.55 C-2.83,8.25 -2.86,9.1 -2.86,8.69 C-2.86,8.27 -3.02,8.85 -2.74,8.57 C-2.44,8.26 -0.43,8.31 -0.02,8.31 C0.4,8.31 2.4,8.17 2.68,8.47 C2.98,8.75 2.93,8.33 2.93,8.75 C2.93,9.17 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c "
+                    android:valueTo="M0 2.02 C0.42,2.02 0.77,1.88 1.05,1.59 C1.35,1.29 1.5,0.94 1.5,0.52 C1.5,0.1 1.35,-0.25 1.05,-0.53 C0.77,-0.83 0.42,-0.98 0,-0.98 C-0.42,-0.98 -0.78,-0.83 -1.08,-0.53 C-1.36,-0.25 -1.5,0.1 -1.5,0.52 C-1.5,0.94 -1.36,1.29 -1.08,1.59 C-0.78,1.88 -0.42,2.02 0,2.02c M0 8.62 C-0.42,8.62 -2.78,8.82 -3.07,8.54 C-3.36,8.24 -3.37,-1.87 -3.37,-2.28 C-4.25,-2.69 -4.29,-5.2 -4.01,-5.48 C-3.71,-5.78 -0.42,-5.75 0,-5.75 C0.42,-5.75 4.39,-5.78 4.36,-5.36 C4.22,-2.97 4.47,-3.03 3.34,-1.78 C3.07,-1.48 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.1,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1000"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:pivotY="24"
+                    android:scaleX="0.33332999999999996"
+                    android:scaleY="0.33332999999999996"
+                    android:translateX="12"
+                    android:translateY="-4">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#1f1f1f"
+                        android:fillType="nonZero"
+                        android:pathData=" M-12 -15 C-12,-15 12,-15 12,-15 C12,-15 12,-13.8 12,-13.8 C12,-13.8 6,-4.8 6,-4.8 C6,-4.8 6,24 6,24 C6,24 -6,24 -6,24 C-6,24 -6,-4.8 -6,-4.8 C-6,-4.8 -12,-13.8 -12,-13.8 C-12,-13.8 -12,-15 -12,-15c  M12 -21 C12,-21 -12,-21 -12,-21 C-12,-21 -12,-24 -12,-24 C-12,-24 12,-24 12,-24 C12,-24 12,-21 12,-21c  M-12 -3 C-12,-3 -12,30 -12,30 C-12,30 12,30 12,30 C12,30 12,-3 12,-3 C12,-3 18,-12 18,-12 C18,-12 18,-30 18,-30 C18,-30 -18,-30 -18,-30 C-18,-30 -18,-12 -18,-12 C-18,-12 -12,-3 -12,-3c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#1f1f1f"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 3 C0.42,3 0.77,2.86 1.05,2.58 C1.35,2.28 1.5,1.92 1.5,1.5 C1.5,1.08 1.35,0.73 1.05,0.45 C0.77,0.15 0.42,0 0,0 C-0.42,0 -0.78,0.15 -1.08,0.45 C-1.36,0.73 -1.5,1.08 -1.5,1.5 C-1.5,1.92 -1.36,2.28 -1.08,2.58 C-0.78,2.86 -0.42,3 0,3c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml
new file mode 100644
index 0000000..0769a85
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml
@@ -0,0 +1,94 @@
+<?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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="translateY"
+                    android:startOffset="0"
+                    android:valueFrom="12.125"
+                    android:valueTo="12.312"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="-45"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="300"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G_T_1"
+                    android:rotation="-45"
+                    android:translateX="12.875"
+                    android:translateY="12.125">
+                    <group
+                        android:name="_R_G_L_0_G"
+                        android:translateX="-2.375">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.59,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.15,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.95,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.12,8.44 4.31,8.56 3.33,8.62c "
+                            android:strokeAlpha="1"
+                            android:strokeColor="#ffffff"
+                            android:strokeLineCap="round"
+                            android:strokeLineJoin="round"
+                            android:strokeWidth="2" />
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml
new file mode 100644
index 0000000..5ffe262
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml
@@ -0,0 +1,94 @@
+<?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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="400"
+                    android:propertyName="translateY"
+                    android:startOffset="0"
+                    android:valueFrom="12.312"
+                    android:valueTo="12.125"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="400"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="-45"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G_T_1"
+                    android:rotation="0"
+                    android:translateX="12.875"
+                    android:translateY="12.312">
+                    <group
+                        android:name="_R_G_L_0_G"
+                        android:translateX="-2.375">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.6,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.14,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.94,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.13,8.43 4.31,8.56 3.33,8.62c "
+                            android:strokeAlpha="1"
+                            android:strokeColor="#ffffff"
+                            android:strokeLineCap="round"
+                            android:strokeLineJoin="round"
+                            android:strokeWidth="2" />
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_screen_record_icon_off.xml b/packages/SystemUI/res/drawable/qs_screen_record_icon_off.xml
new file mode 100644
index 0000000..b3bdd36
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_screen_record_icon_off.xml
@@ -0,0 +1,218 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#edf2eb"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M3.5 -2.5 C3.5,-2.5 3.5,2.5 3.5,2.5 C3.5,3.05 3.05,3.5 2.5,3.5 C2.5,3.5 -2.5,3.5 -2.5,3.5 C-3.05,3.5 -3.5,3.05 -3.5,2.5 C-3.5,2.5 -3.5,-2.5 -3.5,-2.5 C-3.5,-3.05 -3.05,-3.5 -2.5,-3.5 C-2.5,-3.5 2.5,-3.5 2.5,-3.5 C3.05,-3.5 3.5,-3.05 3.5,-2.5c "
+                    android:valueTo="M3.5 0 C3.5,0.97 3.11,1.84 2.48,2.48 C1.84,3.11 0.97,3.5 0,3.5 C-0.97,3.5 -1.84,3.11 -2.47,2.48 C-3.11,1.84 -3.5,0.97 -3.5,0 C-3.5,-0.97 -3.11,-1.84 -2.47,-2.47 C-1.84,-3.11 -0.97,-3.5 0,-3.5 C0.97,-3.5 1.84,-3.11 2.48,-2.47 C3.11,-1.84 3.5,-0.97 3.5,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="90"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#edf2eb"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#edf2eb"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_2">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#edf2eb"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_3">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#000000"
+                    android:valueTo="#edf2eb"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1000"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:rotation="90"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#000000"
+                        android:fillType="nonZero"
+                        android:pathData=" M3.5 -2.5 C3.5,-2.5 3.5,2.5 3.5,2.5 C3.5,3.05 3.05,3.5 2.5,3.5 C2.5,3.5 -2.5,3.5 -2.5,3.5 C-3.05,3.5 -3.5,3.05 -3.5,2.5 C-3.5,2.5 -3.5,-2.5 -3.5,-2.5 C-3.5,-3.05 -3.05,-3.5 -2.5,-3.5 C-2.5,-3.5 2.5,-3.5 2.5,-3.5 C3.05,-3.5 3.5,-3.05 3.5,-2.5c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:fillAlpha="1"
+                            android:fillColor="#000000"
+                            android:fillType="nonZero"
+                            android:pathData=" M6.6 -4.47 C6.6,-4.47 8.04,-5.91 8.04,-5.91 C9.26,-4.26 9.99,-2.21 10,0.01 C10,2.23 9.26,4.27 8.04,5.93 C8.04,5.93 6.6,4.49 6.6,4.49 C7.47,3.2 7.99,1.66 7.99,0 C7.99,-1.65 7.47,-3.19 6.6,-4.47c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_1"
+                            android:fillAlpha="1"
+                            android:fillColor="#000000"
+                            android:fillType="nonZero"
+                            android:pathData=" M-8.01 0 C-8.01,1.67 -7.49,3.22 -6.61,4.5 C-6.61,4.5 -8.04,5.93 -8.04,5.93 C-9.27,4.27 -10,2.22 -10,0 C-10,-2.22 -9.27,-4.26 -8.05,-5.92 C-8.05,-5.92 -6.62,-4.49 -6.62,-4.49 C-7.49,-3.21 -8.01,-1.66 -8.01,0c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_2_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_2"
+                            android:fillAlpha="1"
+                            android:fillColor="#000000"
+                            android:fillType="nonZero"
+                            android:pathData=" M-0.01 8 C1.66,8 3.2,7.48 4.48,6.61 C4.48,6.61 5.91,8.05 5.91,8.05 C4.25,9.27 2.21,10 -0.01,10 C-2.22,10 -4.26,9.27 -5.92,8.05 C-5.92,8.05 -4.48,6.61 -4.48,6.61 C-3.21,7.48 -1.67,8 -0.01,8c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_3_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_3"
+                            android:fillAlpha="1"
+                            android:fillColor="#000000"
+                            android:fillType="nonZero"
+                            android:pathData=" M-5.93 -8.04 C-4.27,-9.27 -2.23,-10 -0.01,-10 C2.22,-10 4.26,-9.27 5.94,-8.03 C5.94,-8.03 4.5,-6.59 4.5,-6.59 C3.21,-7.47 1.67,-7.99 0,-7.99 C-1.67,-7.99 -3.21,-7.47 -4.49,-6.6 C-4.49,-6.6 -5.93,-8.04 -5.93,-8.04c " />
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_screen_record_icon_on.xml b/packages/SystemUI/res/drawable/qs_screen_record_icon_on.xml
new file mode 100644
index 0000000..facbef5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_screen_record_icon_on.xml
@@ -0,0 +1,286 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#edf2eb"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M3.5 0 C3.5,0.97 3.11,1.84 2.48,2.48 C1.84,3.11 0.97,3.5 0,3.5 C-0.97,3.5 -1.84,3.11 -2.47,2.48 C-3.11,1.84 -3.5,0.97 -3.5,0 C-3.5,-0.97 -3.11,-1.84 -2.47,-2.47 C-1.84,-3.11 -0.97,-3.5 0,-3.5 C0.97,-3.5 1.84,-3.11 2.48,-2.47 C3.11,-1.84 3.5,-0.97 3.5,0c "
+                    android:valueTo="M3.5 -2.5 C3.5,-2.5 3.5,2.5 3.5,2.5 C3.5,3.05 3.05,3.5 2.5,3.5 C2.5,3.5 -2.5,3.5 -2.5,3.5 C-3.05,3.5 -3.5,3.05 -3.5,2.5 C-3.5,2.5 -3.5,-2.5 -3.5,-2.5 C-3.5,-3.05 -3.05,-3.5 -2.5,-3.5 C-2.5,-3.5 2.5,-3.5 2.5,-3.5 C3.05,-3.5 3.5,-3.05 3.5,-2.5c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="90"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#edf2eb"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="833"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="180"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#edf2eb"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="833"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="180"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_2">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#edf2eb"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_2_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="833"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="180"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_3">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="417"
+                    android:propertyName="fillColor"
+                    android:startOffset="0"
+                    android:valueFrom="#edf2eb"
+                    android:valueTo="#000000"
+                    android:valueType="colorType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_3_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="833"
+                    android:propertyName="rotation"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="180"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1000"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:rotation="0"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#edf2eb"
+                        android:fillType="nonZero"
+                        android:pathData=" M3.5 0 C3.5,0.97 3.11,1.84 2.48,2.48 C1.84,3.11 0.97,3.5 0,3.5 C-0.97,3.5 -1.84,3.11 -2.47,2.48 C-3.11,1.84 -3.5,0.97 -3.5,0 C-3.5,-0.97 -3.11,-1.84 -2.47,-2.47 C-1.84,-3.11 -0.97,-3.5 0,-3.5 C0.97,-3.5 1.84,-3.11 2.48,-2.47 C3.11,-1.84 3.5,-0.97 3.5,0c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:fillAlpha="1"
+                            android:fillColor="#edf2eb"
+                            android:fillType="nonZero"
+                            android:pathData=" M6.6 -4.47 C6.6,-4.47 8.04,-5.91 8.04,-5.91 C9.26,-4.26 9.99,-2.21 10,0.01 C10,2.23 9.26,4.27 8.04,5.93 C8.04,5.93 6.6,4.49 6.6,4.49 C7.47,3.2 7.99,1.66 7.99,0 C7.99,-1.65 7.47,-3.19 6.6,-4.47c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_1"
+                            android:fillAlpha="1"
+                            android:fillColor="#edf2eb"
+                            android:fillType="nonZero"
+                            android:pathData=" M-8.01 0 C-8.01,1.67 -7.49,3.22 -6.61,4.5 C-6.61,4.5 -8.04,5.93 -8.04,5.93 C-9.27,4.27 -10,2.22 -10,0 C-10,-2.22 -9.27,-4.26 -8.05,-5.92 C-8.05,-5.92 -6.62,-4.49 -6.62,-4.49 C-7.49,-3.21 -8.01,-1.66 -8.01,0c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_2_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_2"
+                            android:fillAlpha="1"
+                            android:fillColor="#edf2eb"
+                            android:fillType="nonZero"
+                            android:pathData=" M-0.01 8 C1.66,8 3.2,7.48 4.48,6.61 C4.48,6.61 5.91,8.05 5.91,8.05 C4.25,9.27 2.21,10 -0.01,10 C-2.22,10 -4.26,9.27 -5.92,8.05 C-5.92,8.05 -4.48,6.61 -4.48,6.61 C-3.21,7.48 -1.67,8 -0.01,8c " />
+                    </group>
+                    <group
+                        android:name="_R_G_L_0_G_D_0_P_3_G_0_T_0"
+                        android:rotation="0">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_3"
+                            android:fillAlpha="1"
+                            android:fillColor="#edf2eb"
+                            android:fillType="nonZero"
+                            android:pathData=" M-5.93 -8.04 C-4.27,-9.27 -2.23,-10 -0.01,-10 C2.22,-10 4.26,-9.27 5.94,-8.03 C5.94,-8.03 4.5,-6.59 4.5,-6.59 C3.21,-7.47 1.67,-7.99 0,-7.99 C-1.67,-7.99 -3.21,-7.47 -4.49,-6.6 C-4.49,-6.6 -5.93,-8.04 -5.93,-8.04c " />
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 11dbe4a..9e8bef0 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -245,6 +245,10 @@
     <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color>
     <color name="dream_overlay_aqi_hazardous">#880E4F</color>
     <color name="dream_overlay_aqi_unknown">#BDC1C6</color>
+
+    <!-- Dream overlay text shadows -->
     <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color>
     <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
+    <color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color>
+    <color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2e57852..6592e14 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1560,10 +1560,20 @@
     <dimen name="broadcast_dialog_btn_text_size">16sp</dimen>
     <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
     <dimen name="broadcast_dialog_margin">16dp</dimen>
+
+    <!-- Shadow for dream overlay clock complication -->
     <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
     <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
-    <dimen name="dream_overlay_clock_key_text_shadow_radius">5dp</dimen>
+    <dimen name="dream_overlay_clock_key_text_shadow_radius">3dp</dimen>
     <dimen name="dream_overlay_clock_ambient_text_shadow_dx">0dp</dimen>
     <dimen name="dream_overlay_clock_ambient_text_shadow_dy">0dp</dimen>
     <dimen name="dream_overlay_clock_ambient_text_shadow_radius">1dp</dimen>
+
+    <!-- Shadow for dream overlay status bar complications -->
+    <dimen name="dream_overlay_status_bar_key_text_shadow_dx">0.5dp</dimen>
+    <dimen name="dream_overlay_status_bar_key_text_shadow_dy">0.5dp</dimen>
+    <dimen name="dream_overlay_status_bar_key_text_shadow_radius">1dp</dimen>
+    <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen>
+    <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
+    <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 34e2e83..c2e7445 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -23,6 +23,7 @@
 import android.app.compat.ChangeIdStateCache.invalidate
 import android.content.Context
 import android.graphics.Canvas
+import android.text.Layout
 import android.text.TextUtils
 import android.text.format.DateFormat
 import android.util.AttributeSet
@@ -78,6 +79,8 @@
     private var textAnimator: TextAnimator? = null
     private var onTextAnimatorInitialized: Runnable? = null
 
+    @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
+        { layout, invalidateCb -> TextAnimator(layout, invalidateCb) }
     @VisibleForTesting var isAnimationEnabled: Boolean = true
     @VisibleForTesting var timeOverrideInMillis: Long? = null
 
@@ -174,7 +177,7 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         val animator = textAnimator
         if (animator == null) {
-            textAnimator = TextAnimator(layout) { invalidate() }
+            textAnimator = textAnimatorFactory(layout, ::invalidate)
             onTextAnimatorInitialized?.run()
             onTextAnimatorInitialized = null
         } else {
@@ -219,9 +222,6 @@
     }
 
     fun animateAppearOnLockscreen() {
-        if (isAnimationEnabled && textAnimator == null) {
-            return
-        }
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0e1e0cb..b444f4c 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -137,6 +137,10 @@
         override fun onThemeChanged() {
             updateFun.updateColors()
         }
+
+        override fun onDensityOrFontScaleChanged() {
+            clock?.events?.onFontSettingChanged()
+        }
     }
 
     private val batteryCallback = object : BatteryStateChangeCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 436b756..8f5cbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -54,6 +54,8 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -127,6 +129,7 @@
     private final float mTranslationY;
     @ContainerState private int mContainerState = STATE_UNKNOWN;
     private final Set<Integer> mFailedModalities = new HashSet<Integer>();
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     private final @Background DelayableExecutor mBackgroundExecutor;
     private int mOrientation;
@@ -362,8 +365,7 @@
                 return false;
             }
             if (event.getAction() == KeyEvent.ACTION_UP) {
-                sendEarlyUserCanceled();
-                animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+                onBackInvoked();
             }
             return true;
         });
@@ -373,6 +375,11 @@
         requestFocus();
     }
 
+    private void onBackInvoked() {
+        sendEarlyUserCanceled();
+        animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+    }
+
     void sendEarlyUserCanceled() {
         mConfig.mCallback.onSystemEvent(
                 BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId());
@@ -520,6 +527,11 @@
                         .start();
             });
         }
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+        if (dispatcher != null) {
+            dispatcher.registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
+        }
     }
 
     private Animator.AnimatorListener getJankListener(View v, String type, long timeout) {
@@ -618,6 +630,10 @@
 
     @Override
     public void onDetachedFromWindow() {
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+        if (dispatcher != null) {
+            findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
+        }
         super.onDetachedFromWindow();
         mWakefulnessLifecycle.removeObserver(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 163e3ab..56103773 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -57,6 +57,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.util.DisplayUtils;
 import android.util.Log;
 import android.util.RotationUtils;
 import android.util.SparseBooleanArray;
@@ -120,8 +121,6 @@
     private final Provider<UdfpsController> mUdfpsControllerFactory;
     private final Provider<SidefpsController> mSidefpsControllerFactory;
 
-    @NonNull private Point mStableDisplaySize = new Point();
-
     private final Display mDisplay;
     private float mScaleFactor = 1f;
     // sensor locations without any resolution scaling nor rotation adjustments:
@@ -287,7 +286,6 @@
                 }
             });
             mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
-            mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination);
             mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
         }
 
@@ -532,10 +530,11 @@
      */
     private void updateSensorLocations() {
         mDisplay.getDisplayInfo(mCachedDisplayInfo);
-
+        final Display.Mode maxDisplayMode =
+                DisplayUtils.getMaximumResolutionDisplayMode(mCachedDisplayInfo.supportedModes);
         final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio(
-                mStableDisplaySize.x, mStableDisplaySize.y, mCachedDisplayInfo.getNaturalWidth(),
-                mCachedDisplayInfo.getNaturalHeight());
+                maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(),
+                mCachedDisplayInfo.getNaturalWidth(), mCachedDisplayInfo.getNaturalHeight());
         if (scaleFactor == Float.POSITIVE_INFINITY) {
             mScaleFactor = 1f;
         } else {
@@ -813,7 +812,6 @@
             );
         }
 
-        mStableDisplaySize = mDisplayManager.getStableDisplaySize();
         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
         mOrientationListener.enable();
         updateSensorLocations();
@@ -1197,7 +1195,6 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         final AuthDialog dialog = mCurrentDialog;
-        pw.println("  stableDisplaySize=" + mStableDisplaySize);
         pw.println("  mCachedDisplayInfo=" + mCachedDisplayInfo);
         pw.println("  mScaleFactor=" + mScaleFactor);
         pw.println("  faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 35d96c9..31e1fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -23,7 +23,6 @@
 import android.graphics.Point
 import android.hardware.biometrics.BiometricFingerprintConstants
 import android.hardware.biometrics.BiometricSourceType
-import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -95,7 +94,6 @@
     public override fun onViewAttached() {
         authController.addCallback(authControllerCallback)
         updateRippleColor()
-        updateSensorLocation()
         updateUdfpsDependentParams()
         udfpsController?.addCallback(udfpsControllerCallback)
         configurationController.addCallback(configurationChangedListener)
@@ -237,7 +235,11 @@
     }
 
     private fun showDwellRipple() {
-        mView.startDwellRipple(statusBarStateController.isDozing)
+        updateSensorLocation()
+        fingerprintSensorLocation?.let {
+            mView.setFingerprintSensorLocation(it, udfpsRadius)
+            mView.startDwellRipple(statusBarStateController.isDozing)
+        }
     }
 
     private val keyguardUpdateMonitorCallback =
@@ -264,7 +266,7 @@
             acquireInfo: Int
         ) {
             if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
-                    BiometricFingerprintConstants.shouldTurnOffHbm(acquireInfo) &&
+                    BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquireInfo) &&
                     acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
                 // received an 'acquiredBad' message, so immediately retract
                 mView.retractDwellRipple()
@@ -291,13 +293,6 @@
     private val udfpsControllerCallback =
         object : UdfpsController.Callback {
             override fun onFingerDown() {
-                if (fingerprintSensorLocation == null) {
-                    Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " +
-                            "Skip showing dwell ripple")
-                    return
-                }
-
-                mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius)
                 showDwellRipple()
             }
 
@@ -310,12 +305,10 @@
         object : AuthController.Callback {
             override fun onAllAuthenticatorsRegistered(modality: Int) {
                 updateUdfpsDependentParams()
-                updateSensorLocation()
             }
 
             override fun onUdfpsLocationChanged() {
                 updateUdfpsDependentParams()
-                updateSensorLocation()
             }
         }
 
@@ -345,13 +338,11 @@
                                 "\n\tudfpsRadius=$udfpsRadius")
                     }
                     "fingerprint" -> {
-                        updateSensorLocation()
                         pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
                         showUnlockRipple(BiometricSourceType.FINGERPRINT)
                     }
                     "face" -> {
                         // note: only shows when about to proceed to the home screen
-                        updateSensorLocation()
                         pw.println("face ripple sensorLocation=$faceSensorLocation")
                         showUnlockRipple(BiometricSourceType.FACE)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index 9281eb8..ad96612 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -54,13 +54,13 @@
         getDrawable().onSensorRectUpdated(bounds);
     }
 
-    void onIlluminationStarting() {
-        getDrawable().setIlluminationShowing(true);
+    void onDisplayConfiguring() {
+        getDrawable().setDisplayConfigured(true);
         getDrawable().invalidateSelf();
     }
 
-    void onIlluminationStopped() {
-        getDrawable().setIlluminationShowing(false);
+    void onDisplayUnconfigured() {
+        getDrawable().setDisplayConfigured(false);
         getDrawable().invalidateSelf();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 742c65c..3ad2bef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -31,10 +31,10 @@
 /**
  * Handles:
  * 1. registering for listeners when its view is attached and unregistering on view detached
- * 2. pausing udfps when fingerprintManager may still be running but we temporarily want to hide
+ * 2. pausing UDFPS when FingerprintManager may still be running but we temporarily want to hide
  * the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth)
  * 3. sending events to its view including:
- * - illumination events
+ * - enabling and disabling of the UDFPS display mode
  * - sensor position changes
  * - doze time event
  */
@@ -167,19 +167,20 @@
     }
 
     /**
-     * Udfps has started illuminating and the fingerprint manager is working on authenticating.
+     * The display began transitioning into the UDFPS mode and the fingerprint manager started
+     * authenticating.
      */
-    fun onIlluminationStarting() {
-        view.onIlluminationStarting()
+    fun onDisplayConfiguring() {
+        view.onDisplayConfiguring()
         view.postInvalidate()
     }
 
     /**
-     * Udfps has stopped illuminating and the fingerprint manager is no longer attempting to
-     * authenticate.
+     * The display transitioned away from the UDFPS mode and the fingerprint manager stopped
+     * authenticating.
      */
-    fun onIlluminationStopped() {
-        view.onIlluminationStopped()
+    fun onDisplayUnconfigured() {
+        view.onDisplayUnconfigured()
         view.postInvalidate()
     }
 
@@ -197,4 +198,4 @@
      * Called when a view should announce an accessibility event.
      */
     open fun doAnnounceForAccessibility(str: String) {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7f2680b..36287f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -86,7 +86,7 @@
 
 /**
  * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
- * and coordinates triggering of the high-brightness mode (HBM).
+ * and toggles the UDFPS display mode.
  *
  * Note that the current architecture is designed so that a single {@link UdfpsController}
  * controls/manages all UDFPS sensors. In other words, a single controller is registered with
@@ -123,7 +123,7 @@
     @NonNull private final PowerManager mPowerManager;
     @NonNull private final AccessibilityManager mAccessibilityManager;
     @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Nullable private final UdfpsHbmProvider mHbmProvider;
+    @Nullable private final UdfpsDisplayModeProvider mUdfpsDisplayMode;
     @NonNull private final ConfigurationController mConfigurationController;
     @NonNull private final SystemClock mSystemClock;
     @NonNull private final UnlockedScreenOffAnimationController
@@ -135,7 +135,6 @@
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting int mSensorId;
-    private boolean mHalControlsIllumination;
     @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -147,10 +146,9 @@
     private int mActivePointerId = -1;
     // The timestamp of the most recent touch log.
     private long mTouchLogTime;
-    // Sensor has a capture (good or bad) for this touch. Do not need to illuminate for this
-    // particular touch event anymore. In other words, do not illuminate until user lifts and
-    // touches the sensor area again.
-    // TODO: We should probably try to make touch/illumination things more of a FSM
+    // Sensor has a capture (good or bad) for this touch. No need to enable the UDFPS display mode
+    // anymore for this particular touch event. In other words, do not enable the UDFPS mode until
+    // the user touches the sensor area again.
     private boolean mAcquiredReceived;
 
     // The current request from FingerprintService. Null if no current request.
@@ -211,8 +209,8 @@
                             mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
                             mLockscreenShadeTransitionController, mConfigurationController,
                             mSystemClock, mKeyguardStateController,
-                            mUnlockedScreenOffAnimationController, mHalControlsIllumination,
-                            mHbmProvider, requestId, reason, callback,
+                            mUnlockedScreenOffAnimationController,
+                            mUdfpsDisplayMode, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
                                     fromUdfpsView), mActivityLaunchAnimator)));
         }
@@ -236,7 +234,7 @@
                 int sensorId,
                 @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo
         ) {
-            if (BiometricFingerprintConstants.shouldTurnOffHbm(acquiredInfo)) {
+            if (BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquiredInfo)) {
                 boolean acquiredGood = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD;
                 mFgExecutor.execute(() -> {
                     if (mOverlay == null) {
@@ -247,7 +245,7 @@
                     mAcquiredReceived = true;
                     final UdfpsView view = mOverlay.getOverlayView();
                     if (view != null) {
-                        view.stopIllumination(); // turn off HBM
+                        view.unconfigureDisplay();
                     }
                     if (acquiredGood) {
                         mOverlay.onAcquiredGood();
@@ -292,7 +290,7 @@
     /**
      * Updates the overlay parameters and reconstructs or redraws the overlay, if necessary.
      *
-     * @param sensorId sensor for which the overlay is getting updated.
+     * @param sensorId      sensor for which the overlay is getting updated.
      * @param overlayParams See {@link UdfpsOverlayParams}.
      */
     public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) {
@@ -321,11 +319,6 @@
         mAuthControllerUpdateUdfpsLocation = r;
     }
 
-    // TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this.
-    public void setHalControlsIllumination(boolean value) {
-        mHalControlsIllumination = value;
-    }
-
     /**
      * Calculate the pointer speed given a velocity tracker and the pointer id.
      * This assumes that the velocity tracker has already been passed all relevant motion events.
@@ -369,8 +362,8 @@
     }
 
     /**
-     * @param x coordinate
-     * @param y coordinate
+     * @param x                   coordinate
+     * @param y                   coordinate
      * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else,
      *                            calculate from the display dimensions in portrait orientation
      */
@@ -423,7 +416,7 @@
         }
 
         final UdfpsView udfpsView = mOverlay.getOverlayView();
-        final boolean isIlluminationRequested = udfpsView.isIlluminationRequested();
+        final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
         boolean handled = false;
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_OUTSIDE:
@@ -507,7 +500,7 @@
                                 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
                                 minor, major, v, exceedsVelocityThreshold);
                         final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
-                        if (!isIlluminationRequested && !mAcquiredReceived
+                        if (!isDisplayConfigured && !mAcquiredReceived
                                 && !exceedsVelocityThreshold) {
 
                             final float scale = mOverlayParams.getScaleFactor();
@@ -598,7 +591,7 @@
             @NonNull VibratorHelper vibrator,
             @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
             @NonNull UdfpsShell udfpsShell,
-            @NonNull Optional<UdfpsHbmProvider> hbmProvider,
+            @NonNull Optional<UdfpsDisplayModeProvider> udfpsDisplayMode,
             @NonNull KeyguardStateController keyguardStateController,
             @NonNull DisplayManager displayManager,
             @Main Handler mainHandler,
@@ -630,7 +623,7 @@
         mPowerManager = powerManager;
         mAccessibilityManager = accessibilityManager;
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
-        mHbmProvider = hbmProvider.orElse(null);
+        mUdfpsDisplayMode = udfpsDisplayMode.orElse(null);
         screenLifecycle.addObserver(mScreenObserver);
         mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
         mConfigurationController = configurationController;
@@ -804,15 +797,14 @@
     }
 
     /**
-     * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
-     * user explicitly lifts their finger. Generally, this should be called whenever udfps fails
-     * or errors.
+     * Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly
+     * lifts their finger. Generally, this should be called on errors in the authentication flow.
      *
      * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
      * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
      * This should be called when authentication either succeeds or fails. Failing to cancel the
-     * scan will leave the screen in high brightness mode and will show the HbmSurfaceView until
-     * the user lifts their finger.
+     * scan will leave the display in the UDFPS mode until the user lifts their finger. On optical
+     * sensors, this can result in illumination persisting for longer than necessary.
      */
     void onCancelUdfps() {
         if (mOverlay != null && mOverlay.getOverlayView() != null) {
@@ -874,7 +866,7 @@
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
         final UdfpsView view = mOverlay.getOverlayView();
         if (view != null) {
-            view.startIllumination(() -> {
+            view.configureDisplay(() -> {
                 if (mAlternateTouchProvider != null) {
                     mBiometricExecutor.execute(() -> {
                         mAlternateTouchProvider.onUiReady();
@@ -914,8 +906,8 @@
             }
         }
         mOnFingerDown = false;
-        if (view.isIlluminationRequested()) {
-            view.stopIllumination();
+        if (view.isDisplayConfigured()) {
+            view.unconfigureDisplay();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index ec72057..1c62f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -77,8 +77,7 @@
     private val systemClock: SystemClock,
     private val keyguardStateController: KeyguardStateController,
     private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
-    private val halControlsIllumination: Boolean,
-    private var hbmProvider: UdfpsHbmProvider,
+    private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
     val requestId: Long,
     @ShowReason val requestReason: Int,
     private val controllerCallback: IUdfpsOverlayControllerCallback,
@@ -102,8 +101,8 @@
         fitInsetsTypes = 0
         gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
         layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        flags =
-            (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+        flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
+          WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
         privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
         // Avoid announcing window title.
         accessibilityTitle = " "
@@ -140,8 +139,7 @@
                     R.layout.udfps_view, null, false
                 ) as UdfpsView).apply {
                     overlayParams = params
-                    halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination
-                    setHbmProvider(hbmProvider)
+                    setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
                     val animation = inflateUdfpsAnimation(this, controller)
                     if (animation != null) {
                         animation.init()
@@ -250,8 +248,8 @@
         val wasShowing = isShowing
 
         overlayView?.apply {
-            if (isIlluminationRequested) {
-                stopIllumination()
+            if (isDisplayConfigured) {
+                unconfigureDisplay()
             }
             windowManager.removeView(this)
             setOnTouchListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayModeProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayModeProvider.java
new file mode 100644
index 0000000..c6957ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayModeProvider.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.systemui.biometrics;
+
+import android.annotation.Nullable;
+
+/**
+ * Interface for toggling the optimal display mode for the under-display fingerprint sensor
+ * (UDFPS). For example, the implementation might change the refresh rate and activate a
+ * high-brightness mode.
+ */
+public interface UdfpsDisplayModeProvider {
+
+    /**
+     * Enables the optimal display mode for UDFPS. The mode will persist until
+     * {@link #disable(Runnable)} is called.
+     *
+     * This call must be made from the UI thread. The callback, if provided, will also be invoked
+     * from the UI thread.
+     *
+     * @param onEnabled A runnable that will be executed once the mode is enabled.
+     */
+    void enable(@Nullable Runnable onEnabled);
+
+    /**
+     * Disables the mode that was enabled by {@link #enable(Runnable)}.
+     *
+     * The call must be made from the UI thread. The callback, if provided, will also be invoked
+     * from the UI thread.
+     *
+     * @param onDisabled A runnable that will be executed once mode is disabled.
+     */
+    void disable(@Nullable Runnable onDisabled);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt
index ee112b4..511b4e3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt
@@ -51,7 +51,7 @@
             invalidateSelf()
         }
 
-    var isIlluminationShowing: Boolean = false
+    var isDisplayConfigured: Boolean = false
         set(showing) {
             if (field == showing) {
                 return
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 1317492..1e35958 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -197,7 +197,7 @@
 
     @Override
     public void draw(@NonNull Canvas canvas) {
-        if (isIlluminationShowing()) {
+        if (isDisplayConfigured()) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt
index 1afa36b..9f6b6d7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt
@@ -23,7 +23,7 @@
  */
 class UdfpsFpDrawable(context: Context) : UdfpsDrawable(context) {
     override fun draw(canvas: Canvas) {
-        if (isIlluminationShowing) {
+        if (isDisplayConfigured) {
             return
         }
         fingerprintDrawable.draw(canvas)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
deleted file mode 100644
index f26dd5f..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.Nullable;
-
-/**
- * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
- * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
- * illumination is no longer necessary.
- */
-public interface UdfpsHbmProvider {
-
-    /**
-     * UdfpsView will call this to enable the HBM when the fingerprint illumination is needed.
-     *
-     * This method is a no-op when some type of HBM is already enabled.
-     *
-     * This method must be called from the UI thread. The callback, if provided, will also be
-     * invoked from the UI thread.
-     *
-     * @param onHbmEnabled A runnable that will be executed once HBM is enabled.
-     *
-     * TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
-     *     This only makes sense now because vendor code may rely on the side effects of enableHbm.
-     */
-    void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled);
-
-    /**
-     * UdfpsView will call this to disable HBM when illumination is no longer needed.
-     *
-     * This method will disable HBM if HBM is enabled. Otherwise, if HBM is already disabled,
-     * this method is a no-op.
-     *
-     * The call must be made from the UI thread. The callback, if provided, will also be invoked
-     * from the UI thread.
-     *
-     * @param onHbmDisabled A runnable that will be executed once HBM is disabled.
-     */
-    void disableHbm(@Nullable Runnable onHbmDisabled);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
deleted file mode 100644
index f85e936..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.Nullable;
-
-/**
- * Interface that should be implemented by UI's that need to coordinate user touches,
- * views/animations, and modules that start/stop display illumination.
- */
-interface UdfpsIlluminator {
-    /**
-     * @param hbmProvider Invoked when HBM should be enabled or disabled.
-     */
-    void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider);
-
-    /**
-     * Invoked when illumination should start.
-     * @param onIlluminatedRunnable Invoked when the display has been illuminated.
-     */
-    void startIllumination(@Nullable Runnable onIlluminatedRunnable);
-
-    /**
-     * Invoked when illumination should end.
-     */
-    void stopIllumination();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index f28fedb..bc274a0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -101,11 +101,11 @@
     }
 
     @Override
-    void onIlluminationStarting() {
+    void onDisplayConfiguring() {
     }
 
     @Override
-    void onIlluminationStopped() {
+    void onDisplayUnconfigured() {
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 245c225..a15456d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -36,12 +36,12 @@
 class UdfpsView(
     context: Context,
     attrs: AttributeSet?
-) : FrameLayout(context, attrs), DozeReceiver, UdfpsIlluminator {
+) : FrameLayout(context, attrs), DozeReceiver {
 
     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
     // overlayParams.sensorBounds
     private val sensorRect = RectF()
-    private var hbmProvider: UdfpsHbmProvider? = null
+    private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
     private val debugTextPaint = Paint().apply {
         isAntiAlias = true
         color = Color.BLUE
@@ -56,19 +56,12 @@
             a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f)
         }
 
-    private val onIlluminatedDelayMs = context.resources.getInteger(
-        com.android.internal.R.integer.config_udfps_illumination_transition_ms
-    ).toLong()
-
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
     /** Parameters that affect the position and size of the overlay. */
     var overlayParams = UdfpsOverlayParams()
 
-    /** Whether the HAL is responsible for enabling and disabling of LHBM. */
-    var halControlsIllumination: Boolean = true
-
     /** Debug message. */
     var debugMessage: String? = null
         set(value) {
@@ -76,12 +69,12 @@
             postInvalidate()
         }
 
-    /** When [startIllumination] has been called but not stopped via [stopIllumination]. */
-    var isIlluminationRequested: Boolean = false
+    /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
+    var isDisplayConfigured: Boolean = false
         private set
 
-    override fun setHbmProvider(provider: UdfpsHbmProvider?) {
-        hbmProvider = provider
+    fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) {
+        mUdfpsDisplayMode = udfpsDisplayModeProvider
     }
 
     // Don't propagate any touch events to the child views.
@@ -124,7 +117,7 @@
 
     override fun onDraw(canvas: Canvas) {
         super.onDraw(canvas)
-        if (!isIlluminationRequested) {
+        if (!isDisplayConfigured) {
             if (!debugMessage.isNullOrEmpty()) {
                 canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint)
             }
@@ -147,36 +140,15 @@
             !(animationViewController?.shouldPauseAuth() ?: false)
     }
 
-    /**
-     * Start and run [onIlluminatedRunnable] when the first illumination frame reaches the panel.
-     */
-    override fun startIllumination(onIlluminatedRunnable: Runnable?) {
-        isIlluminationRequested = true
-        animationViewController?.onIlluminationStarting()
-        doIlluminate(onIlluminatedRunnable)
+    fun configureDisplay(onDisplayConfigured: Runnable) {
+        isDisplayConfigured = true
+        animationViewController?.onDisplayConfiguring()
+        mUdfpsDisplayMode?.enable(onDisplayConfigured)
     }
 
-    private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
-        // TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
-        // This only makes sense now because vendor code may rely on the side effects of enableHbm.
-        hbmProvider?.enableHbm(halControlsIllumination) {
-            if (onIlluminatedRunnable != null) {
-                if (halControlsIllumination) {
-                    onIlluminatedRunnable.run()
-                } else {
-                    // No framework API can reliably tell when a frame reaches the panel. A timeout
-                    // is the safest solution.
-                    postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
-                }
-            } else {
-                Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null")
-            }
-        }
-    }
-
-    override fun stopIllumination() {
-        isIlluminationRequested = false
-        animationViewController?.onIlluminationStopped()
-        hbmProvider?.disableHbm(null /* onHbmDisabled */)
+    fun unconfigureDisplay() {
+        isDisplayConfigured = false
+        animationViewController?.onDisplayUnconfigured()
+        mUdfpsDisplayMode?.disable(null /* onDisabled */)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
new file mode 100644
index 0000000..96af42b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.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.bluetooth
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.BluetoothLog
+import javax.inject.Inject
+
+/** Helper class for logging bluetooth events. */
+@SysUISingleton
+class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) {
+    fun logActiveDeviceChanged(address: String?, profileId: Int) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                int1 = profileId
+            },
+            { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
+        )
+
+    fun logDeviceConnectionStateChanged(address: String?, state: String) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                str2 = state
+            },
+            { "DeviceConnectionStateChanged. address=$str1 state=$str2" }
+        )
+
+    fun logAclConnectionStateChanged(address: String, state: String) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                str2 = state
+            },
+            { "AclConnectionStateChanged. address=$str1 state=$str2" }
+        )
+
+    fun logProfileConnectionStateChanged(address: String?, state: String, profileId: Int) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                str2 = state
+                int1 = profileId
+            },
+            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
+        )
+
+    fun logStateChange(state: String) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = state },
+            { "BluetoothStateChanged. state=$str1" }
+        )
+
+    fun logBondStateChange(address: String, state: Int) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                int1 = state
+            },
+            { "DeviceBondStateChanged. address=$str1 state=$int1" }
+        )
+
+    fun logDeviceAdded(address: String) =
+        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" })
+
+    fun logDeviceDeleted(address: String) =
+        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" })
+
+    fun logDeviceAttributesChanged() =
+        logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." })
+}
+
+private const val TAG = "BluetoothLog"
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 31a2134..bfbf37a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -291,7 +291,7 @@
                         FalsingClassifier.Result.falsed(
                                 0, getClass().getSimpleName(), "bad history"));
                 logDebug("False Single Tap: true (bad history)");
-                mFalsingTapListeners.forEach(FalsingTapListener::onDoubleTapRequired);
+                mFalsingTapListeners.forEach(FalsingTapListener::onAdditionalTapRequired);
                 return true;
             } else {
                 mPriorResults = getPassedResult(0.1);
@@ -321,7 +321,7 @@
                 mHistoryTracker.falseBelief(),
                 mHistoryTracker.falseConfidence());
         mPriorResults = Collections.singleton(result);
-        logDebug("False Double Tap: " + result.isFalse());
+        logDebug("False Double Tap: " + result.isFalse() + " reason=" + result.getReason());
         return result.isFalse();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 23d87ff..f5f9655 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -303,9 +303,7 @@
 
     @Override
     public void onTouchEvent(MotionEvent ev) {
-        if (!mKeyguardStateController.isShowing()
-                || (mStatusBarStateController.isDozing()
-                    && !mStatusBarStateController.isPulsing())) {
+        if (!mKeyguardStateController.isShowing()) {
             avoidGesture();
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index c21e36a..7e499eb 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -119,15 +119,12 @@
  */
 public class ClipboardOverlayController {
     private static final String TAG = "ClipboardOverlayCtrlr";
-    private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
 
     /** Constants for screenshot/copy deconflicting */
     public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
     public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
     public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
 
-    private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
-
     private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
     private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
     private static final int FONT_SEARCH_STEP_PX = 4;
@@ -383,7 +380,7 @@
                     mTextPreview);
             accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
         }
-        Intent remoteCopyIntent = getRemoteCopyIntent(clipData);
+        Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
         // Only show remote copy if it's available.
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.resolveActivity(
@@ -500,41 +497,19 @@
 
     private void editImage(Uri uri) {
         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
-        String editorPackage = mContext.getString(R.string.config_screenshotEditor);
-        Intent editIntent = new Intent(Intent.ACTION_EDIT);
-        if (!TextUtils.isEmpty(editorPackage)) {
-            editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
-        }
-        editIntent.setDataAndType(uri, "image/*");
-        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true);
-        mContext.startActivity(editIntent);
+        mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
         animateOut();
     }
 
     private void editText() {
         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
-        Intent editIntent = new Intent(mContext, EditTextActivity.class);
-        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivity(editIntent);
+        mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
         animateOut();
     }
 
     private void shareContent(ClipData clip) {
         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
-        Intent shareIntent = new Intent(Intent.ACTION_SEND);
-        shareIntent.setDataAndType(
-                clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0));
-        shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString());
-        if (clip.getItemAt(0).getUri() != null) {
-            shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri());
-            shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        }
-        Intent chooserIntent = Intent.createChooser(shareIntent, null)
-                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
-                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        mContext.startActivity(chooserIntent);
+        mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
         animateOut();
     }
 
@@ -667,20 +642,6 @@
                 mContext.getString(R.string.clipboard_edit), null);
     }
 
-    private Intent getRemoteCopyIntent(ClipData clipData) {
-        Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
-
-        String remoteCopyPackage = mContext.getString(R.string.config_remoteCopyPackage);
-        if (!TextUtils.isEmpty(remoteCopyPackage)) {
-            nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage));
-        }
-
-        nearbyIntent.setClipData(clipData);
-        nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        return nearbyIntent;
-    }
-
     private void animateIn() {
         if (mAccessibilityManager.isEnabled()) {
             mDismissButton.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
new file mode 100644
index 0000000..3d5e601
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.systemui.R;
+
+class IntentCreator {
+    private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+    private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+    static Intent getTextEditorIntent(Context context) {
+        Intent intent = new Intent(context, EditTextActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        return intent;
+    }
+
+    static Intent getShareIntent(ClipData clipData, Context context) {
+        Intent shareIntent = new Intent(Intent.ACTION_SEND);
+
+        // From the ACTION_SEND docs:
+        //   "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the
+        //    MIME type of the data in EXTRA_STREAM"
+        if (clipData.getItemAt(0).getUri() != null) {
+            shareIntent.setDataAndType(
+                    clipData.getItemAt(0).getUri(), clipData.getDescription().getMimeType(0));
+            shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri());
+            shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } else {
+            shareIntent.putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context));
+            shareIntent.setType("text/plain");
+        }
+        Intent chooserIntent = Intent.createChooser(shareIntent, null)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        return chooserIntent;
+    }
+
+    static Intent getImageEditIntent(Uri uri, Context context) {
+        String editorPackage = context.getString(R.string.config_screenshotEditor);
+        Intent editIntent = new Intent(Intent.ACTION_EDIT);
+        if (!TextUtils.isEmpty(editorPackage)) {
+            editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+        }
+        editIntent.setDataAndType(uri, "image/*");
+        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true);
+        return editIntent;
+    }
+
+    static Intent getRemoteCopyIntent(ClipData clipData, Context context) {
+        Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
+
+        String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage);
+        if (!TextUtils.isEmpty(remoteCopyPackage)) {
+            nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage));
+        }
+
+        nearbyIntent.setClipData(clipData);
+        nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        return nearbyIntent;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 318529b..0469152 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -30,7 +30,7 @@
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
-import com.android.systemui.biometrics.UdfpsHbmProvider;
+import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.controls.dagger.ControlsModule;
@@ -197,7 +197,7 @@
     abstract CentralSurfaces optionalCentralSurfaces();
 
     @BindsOptionalOf
-    abstract UdfpsHbmProvider optionalUdfpsHbmProvider();
+    abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider();
 
     @BindsOptionalOf
     abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
index 653f4dc..789ebc5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
@@ -17,24 +17,21 @@
 package com.android.systemui.dreams.complication;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.widget.TextClock;
 
 import com.android.systemui.R;
+import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo;
+
+import kotlin.Unit;
 
 /**
  * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows)
  */
 public class DoubleShadowTextClock extends TextClock {
-    private final float mAmbientShadowBlur;
-    private final int mAmbientShadowColor;
-    private final float mKeyShadowBlur;
-    private final float mKeyShadowOffsetX;
-    private final float mKeyShadowOffsetY;
-    private final int mKeyShadowColor;
-    private final float mAmbientShadowOffsetX;
-    private final float mAmbientShadowOffsetY;
+    private final DoubleShadowTextHelper mShadowHelper;
 
     public DoubleShadowTextClock(Context context) {
         this(context, null);
@@ -46,38 +43,28 @@
 
     public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mKeyShadowBlur = context.getResources()
-                .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius);
-        mKeyShadowOffsetX = context.getResources()
-                .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx);
-        mKeyShadowOffsetY = context.getResources()
-                .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy);
-        mKeyShadowColor = context.getResources().getColor(
-                R.color.dream_overlay_clock_key_text_shadow_color);
-        mAmbientShadowBlur = context.getResources()
-                .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_radius);
-        mAmbientShadowColor = context.getResources().getColor(
-                R.color.dream_overlay_clock_ambient_text_shadow_color);
-        mAmbientShadowOffsetX = context.getResources()
-                .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx);
-        mAmbientShadowOffsetY = context.getResources()
-                .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy);
+
+        final Resources resources = context.getResources();
+        final ShadowInfo keyShadowInfo = new ShadowInfo(
+                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius),
+                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx),
+                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy),
+                resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color));
+
+        final ShadowInfo ambientShadowInfo = new ShadowInfo(
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_clock_ambient_text_shadow_radius),
+                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx),
+                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy),
+                resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color));
+        mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
     }
 
     @Override
     public void onDraw(Canvas canvas) {
-        // We enhance the shadow by drawing the shadow twice
-        getPaint().setShadowLayer(mAmbientShadowBlur, mAmbientShadowOffsetX, mAmbientShadowOffsetY,
-                mAmbientShadowColor);
-        super.onDraw(canvas);
-        canvas.save();
-        canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
-                getScrollX() + getWidth(),
-                getScrollY() + getHeight());
-
-        getPaint().setShadowLayer(
-                mKeyShadowBlur, mKeyShadowOffsetX, mKeyShadowOffsetY, mKeyShadowColor);
-        super.onDraw(canvas);
-        canvas.restore();
+        mShadowHelper.applyShadows(this, canvas, () -> {
+            super.onDraw(canvas);
+            return Unit.INSTANCE;
+        });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
new file mode 100644
index 0000000..b1dc5a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication
+
+import android.graphics.Canvas
+import android.widget.TextView
+import androidx.annotation.ColorInt
+
+class DoubleShadowTextHelper
+constructor(
+    private val keyShadowInfo: ShadowInfo,
+    private val ambientShadowInfo: ShadowInfo,
+) {
+    data class ShadowInfo(
+        val blur: Float,
+        val offsetX: Float = 0f,
+        val offsetY: Float = 0f,
+        @ColorInt val color: Int
+    )
+
+    fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) {
+        // We enhance the shadow by drawing the shadow twice
+        view.paint.setShadowLayer(
+            ambientShadowInfo.blur,
+            ambientShadowInfo.offsetX,
+            ambientShadowInfo.offsetY,
+            ambientShadowInfo.color
+        )
+        onDrawCallback()
+        canvas.save()
+        canvas.clipRect(
+            view.scrollX,
+            view.scrollY + view.extendedPaddingTop,
+            view.scrollX + view.width,
+            view.scrollY + view.height
+        )
+
+        view.paint.setShadowLayer(
+            keyShadowInfo.blur,
+            keyShadowInfo.offsetX,
+            keyShadowInfo.offsetY,
+            keyShadowInfo.color
+        )
+        onDrawCallback()
+        canvas.restore()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
new file mode 100644
index 0000000..cf7e312
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import kotlin.Unit;
+
+/**
+ * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows}
+ */
+public class DoubleShadowTextView extends TextView {
+    private final DoubleShadowTextHelper mShadowHelper;
+
+    public DoubleShadowTextView(Context context) {
+        this(context, null);
+    }
+
+    public DoubleShadowTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources resources = context.getResources();
+        final DoubleShadowTextHelper.ShadowInfo
+                keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_status_bar_key_text_shadow_radius),
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_status_bar_key_text_shadow_dx),
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_status_bar_key_text_shadow_dy),
+                resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color));
+
+        final DoubleShadowTextHelper.ShadowInfo
+                ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius),
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx),
+                resources.getDimensionPixelSize(
+                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy),
+                resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color));
+        mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        mShadowHelper.applyShadows(this, canvas, () -> {
+            super.onDraw(canvas);
+            return Unit.INSTANCE;
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index a3dc779..568143c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -42,6 +42,8 @@
 import android.util.Slog;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -601,13 +603,14 @@
 
     private final class BluetoothCallbackHandler implements BluetoothCallback {
         @Override
-        public void onBluetoothStateChanged(int bluetoothState) {
+        public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) {
             mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
                     bluetoothState, 0).sendToTarget();
         }
 
         @Override
-        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        public void onDeviceBondStateChanged(
+                @NonNull CachedBluetoothDevice cachedDevice, int bondState) {
             mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED,
                     bondState, 0, cachedDevice).sendToTarget();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
new file mode 100644
index 0000000..4887b6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for bluetooth. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
index 323ee21..b551125 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -1,4 +1,9 @@
 package com.android.systemui.log.dagger
 
+import javax.inject.Qualifier
+
 /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
 annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 756feb0..5612c22 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -316,4 +316,14 @@
     public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
         return factory.create("KeyguardUpdateMonitorLog", 200);
     }
+
+    /**
+     * Provides a {@link LogBuffer} for bluetooth-related logs.
+     */
+    @Provides
+    @SysUISingleton
+    @BluetoothLog
+    public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
+        return factory.create("BluetoothLog", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
index f03fbcb..b237f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -19,7 +19,6 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -27,7 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor}
+ * A {@link LogBuffer} for status bar connectivity events.
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
index d082655..556560c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
@@ -34,7 +34,7 @@
  * is triggered.
  */
 interface ColorTransition {
-    fun updateColorScheme(scheme: ColorScheme?)
+    fun updateColorScheme(scheme: ColorScheme?): Boolean
 }
 
 /**
@@ -64,14 +64,16 @@
         applyColor(currentColor)
     }
 
-    override fun updateColorScheme(scheme: ColorScheme?) {
+    override fun updateColorScheme(scheme: ColorScheme?): Boolean {
         val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
         if (newTargetColor != targetColor) {
             sourceColor = currentColor
             targetColor = newTargetColor
             valueAnimator.cancel()
             valueAnimator.start()
+            return true
         }
+        return false
     }
 
     init {
@@ -198,8 +200,10 @@
         return Utils.getColorAttr(context, id).defaultColor
     }
 
-    fun updateColorScheme(colorScheme: ColorScheme?) {
-        colorTransitions.forEach { it.updateColorScheme(colorScheme) }
+    fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
+        var anyChanged = false
+        colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
         colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
+        return anyChanged
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index cc77ed1..654c158 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -458,7 +458,7 @@
         val existingPlayer = MediaPlayerData.getMediaPlayer(key)
         val curVisibleMediaKey = MediaPlayerData.playerKeys()
                 .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
-        val isCurVisibleMediaPlaying = MediaPlayerData.getMediaData(curVisibleMediaKey)?.isPlaying
+        val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
         if (existingPlayer == null) {
             val newPlayer = mediaControlPanelFactory.get()
             newPlayer.attachPlayer(MediaViewHolder.create(
@@ -1046,15 +1046,6 @@
         }
     }
 
-    fun getMediaData(mediaSortKey: MediaSortKey?): MediaData? {
-        mediaData.forEach { (key, value) ->
-            if (value == mediaSortKey) {
-                return mediaData[key]?.data
-            }
-        }
-        return null
-    }
-
     fun getMediaPlayer(key: String): MediaControlPanel? {
         return mediaData.get(key)?.let { mediaPlayers.get(it) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index b02393b..759795f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -741,10 +741,14 @@
                 }
                 mArtworkBoundId = reqId;
 
+                // Transition Colors to current color scheme
+                boolean colorSchemeChanged = mColorSchemeTransition.updateColorScheme(colorScheme);
+
                 // Bind the album view to the artwork or a transition drawable
                 ImageView albumView = mMediaViewHolder.getAlbumView();
                 albumView.setPadding(0, 0, 0, 0);
-                if (updateBackground || (!mIsArtworkBound && isArtworkBound)) {
+                if (updateBackground || colorSchemeChanged
+                        || (!mIsArtworkBound && isArtworkBound)) {
                     if (mPrevArtwork == null) {
                         albumView.setImageDrawable(artwork);
                     } else {
@@ -767,9 +771,6 @@
                     mIsArtworkBound = isArtworkBound;
                 }
 
-                // Transition Colors to current color scheme
-                mColorSchemeTransition.updateColorScheme(colorScheme);
-
                 // App icon - use notification icon
                 ImageView appIconView = mMediaViewHolder.getAppIcon();
                 appIconView.clearColorFilter();
diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
index 88f6f3d..6bc94cd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
@@ -60,6 +60,8 @@
             linePaint.strokeWidth = value
         }
 
+    // Enables a transition region where the amplitude
+    // of the wave is reduced linearly across it.
     var transitionEnabled = true
         set(value) {
             field = value
@@ -116,44 +118,40 @@
         }
 
         val progress = level / 10_000f
-        val totalProgressPx = bounds.width() * progress
-        val waveProgressPx = bounds.width() * (
+        val totalWidth = bounds.width().toFloat()
+        val totalProgressPx = totalWidth * progress
+        val waveProgressPx = totalWidth * (
             if (!transitionEnabled || progress > matchedWaveEndpoint) progress else
             lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress)))
 
         // Build Wiggly Path
-        val waveStart = -phaseOffset
-        val waveEnd = waveProgressPx
-        val transitionLength = if (transitionEnabled) transitionPeriods * waveLength else 0.01f
+        val waveStart = -phaseOffset - waveLength / 2f
+        val waveEnd = if (transitionEnabled) totalWidth else waveProgressPx
 
         // helper function, computes amplitude for wave segment
         val computeAmplitude: (Float, Float) -> Float = { x, sign ->
-            sign * heightFraction * lineAmplitude *
-                    lerpInvSat(waveEnd, waveEnd - transitionLength, x)
+            if (transitionEnabled) {
+                val length = transitionPeriods * waveLength
+                val coeff = lerpInvSat(
+                    waveProgressPx + length / 2f,
+                    waveProgressPx - length / 2f,
+                    x)
+                sign * heightFraction * lineAmplitude * coeff
+            } else {
+                sign * heightFraction * lineAmplitude
+            }
         }
 
-        var currentX = waveEnd
-        var waveSign = if (phaseOffset < waveLength / 2) 1f else -1f
+        // Reset path object to the start
         path.rewind()
+        path.moveTo(waveStart, 0f)
 
-        // Draw flat line from end to wave endpoint
-        path.moveTo(bounds.width().toFloat(), 0f)
-        path.lineTo(waveEnd, 0f)
-
-        // First wave has shortened wavelength
-        // approx quarter wave gets us to first wave peak
-        // shouldn't be big enough to notice it's not a sin wave
-        currentX -= phaseOffset % (waveLength / 2)
-        val controlRatio = 0.25f
+        // Build the wave, incrementing by half the wavelength each time
+        var currentX = waveStart
+        var waveSign = 1f
         var currentAmp = computeAmplitude(currentX, waveSign)
-        path.cubicTo(
-            waveEnd, currentAmp * controlRatio,
-            lerp(currentX, waveEnd, controlRatio), currentAmp,
-            currentX, currentAmp)
-
-        // Other waves have full wavelength
-        val dist = -1 * waveLength / 2f
-        while (currentX > waveStart) {
+        val dist = waveLength / 2f
+        while (currentX < waveEnd) {
             waveSign = -waveSign
             val nextX = currentX + dist
             val midX = currentX + dist / 2
@@ -166,34 +164,35 @@
             currentX = nextX
         }
 
-        // Draw path; clip to progress position
+        // translate to the start position of the progress bar for all draw commands
+        val clipTop = lineAmplitude + strokeWidth
         canvas.save()
         canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat())
-        canvas.clipRect(
-                0f,
-                -lineAmplitude - strokeWidth,
-                totalProgressPx,
-                lineAmplitude + strokeWidth)
+
+        // Draw path up to progress position
+        canvas.save()
+        canvas.clipRect(0f, -1f * clipTop, totalProgressPx, clipTop)
         canvas.drawPath(path, wavePaint)
         canvas.restore()
 
-        // Draw path; clip between progression position & far edge
-        canvas.save()
-        canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat())
-        canvas.clipRect(
-                totalProgressPx,
-                -lineAmplitude - strokeWidth,
-                bounds.width().toFloat(),
-                lineAmplitude + strokeWidth)
-        canvas.drawPath(path, linePaint)
-        canvas.restore()
+        if (transitionEnabled) {
+            // If there's a smooth transition, we draw the rest of the
+            // path in a different color (using different clip params)
+            canvas.save()
+            canvas.clipRect(totalProgressPx, -1f * clipTop, totalWidth, clipTop)
+            canvas.drawPath(path, linePaint)
+            canvas.restore()
+        } else {
+            // No transition, just draw a flat line to the end of the region.
+            // The discontinuity is hidden by the progress bar thumb shape.
+            canvas.drawLine(totalProgressPx, 0f, totalWidth, 0f, linePaint)
+        }
 
         // Draw round line cap at the beginning of the wave
-        val startAmp = cos(abs(waveEnd - phaseOffset) / waveLength * TWO_PI)
-        canvas.drawPoint(
-                bounds.left.toFloat(),
-                bounds.centerY() + startAmp * lineAmplitude * heightFraction,
-                wavePaint)
+        val startAmp = cos(abs(waveStart) / waveLength * TWO_PI)
+        canvas.drawPoint(0f, startAmp * lineAmplitude * heightFraction, wavePaint)
+
+        canvas.restore()
     }
 
     override fun getOpacity(): Int {
@@ -233,4 +232,4 @@
         linePaint.color = ColorUtils.setAlphaComponent(tintColor,
                 (DISABLED_ALPHA * (alpha / 255f)).toInt())
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 57bea67..6e4c858 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -24,6 +24,7 @@
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.metrics.LogMaker;
+import android.util.Log;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -61,6 +62,7 @@
  */
 public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T>
         implements Dumpable{
+    private static final String TAG = "QSPanelControllerBase";
     protected final QSTileHost mHost;
     private final QSCustomizerController mQsCustomizerController;
     private final boolean mUsingMediaPlayer;
@@ -90,6 +92,13 @@
                 public void onConfigurationChange(Configuration newConfig) {
                     mShouldUseSplitNotificationShade =
                             LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+                    // Logging to aid the investigation of b/216244185.
+                    Log.d(TAG,
+                            "onConfigurationChange: "
+                                    + "mShouldUseSplitNotificationShade="
+                                    + mShouldUseSplitNotificationShade + ", "
+                                    + "newConfig.windowConfiguration="
+                                    + newConfig.windowConfiguration);
                     mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
                             mView.getDumpableTag());
                     onConfigurationChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index c2a82a7..a31500c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -49,7 +49,6 @@
 /** Quick settings tile: Invert colors **/
 public class ColorInversionTile extends QSTileImpl<BooleanState> {
 
-    private final Icon mIcon = ResourceIcon.get(drawable.ic_invert_colors);
     private final SettingObserver mSetting;
 
     @Inject
@@ -123,7 +122,9 @@
         state.value = enabled;
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_inversion_label);
-        state.icon = mIcon;
+        state.icon = ResourceIcon.get(state.value
+                ? drawable.qs_invert_colors_icon_on
+                : drawable.qs_invert_colors_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 9fdf594..2fc99f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -128,13 +128,13 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
-        state.value = arg instanceof Boolean ? (Boolean) arg
+        state.value = arg instanceof Boolean ? ((Boolean) arg).booleanValue()
                 : mDataSaverController.isDataSaverEnabled();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.data_saver);
         state.contentDescription = state.label;
-        state.icon = ResourceIcon.get(state.value ? R.drawable.ic_data_saver
-                : R.drawable.ic_data_saver_off);
+        state.icon = ResourceIcon.get(state.value ? R.drawable.qs_data_saver_icon_on
+                : R.drawable.qs_data_saver_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 73b0896..a747926 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -43,11 +43,12 @@
 
 import javax.inject.Inject;
 
-/** Quick settings tile: Control flashlight **/
+/**
+ * Quick settings tile: Control flashlight
+ **/
 public class FlashlightTile extends QSTileImpl<BooleanState> implements
         FlashlightController.FlashlightListener {
 
-    private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_flashlight);
     private final FlashlightController mFlashlightController;
 
     @Inject
@@ -116,19 +117,15 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
-        if (state.slash == null) {
-            state.slash = new SlashState();
-        }
         state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
         state.secondaryLabel = "";
         state.stateDescription = "";
         if (!mFlashlightController.isAvailable()) {
-            state.icon = mIcon;
-            state.slash.isSlashed = true;
             state.secondaryLabel = mContext.getString(
                     R.string.quick_settings_flashlight_camera_in_use);
             state.stateDescription = state.secondaryLabel;
             state.state = Tile.STATE_UNAVAILABLE;
+            state.icon = ResourceIcon.get(R.drawable.qs_flashlight_icon_off);
             return;
         }
         if (arg instanceof Boolean) {
@@ -140,11 +137,11 @@
         } else {
             state.value = mFlashlightController.isEnabled();
         }
-        state.icon = mIcon;
-        state.slash.isSlashed = !state.value;
         state.contentDescription = mContext.getString(R.string.quick_settings_flashlight_label);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.icon = ResourceIcon.get(state.value
+                ? R.drawable.qs_flashlight_icon_on : R.drawable.qs_flashlight_icon_off);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 81813db..0e9f659 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -144,9 +144,10 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.value = mManager.isNightDisplayActivated();
         state.label = mContext.getString(R.string.quick_settings_night_display_label);
-        state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_night_display_on);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on
+                : R.drawable.qs_nightlight_icon_off);
         state.secondaryLabel = getSecondaryLabel(state.value);
         state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
                 ? state.label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 6921ad5..1dac339 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -49,7 +49,6 @@
 public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
         implements ReduceBrightColorsController.Listener{
 
-    private final Icon mIcon = ResourceIcon.get(drawable.ic_reduce_bright_colors);
     private final boolean mIsAvailable;
     private final ReduceBrightColorsController mReduceBrightColorsController;
     private boolean mIsListening;
@@ -111,7 +110,9 @@
         state.label = mContext.getString(R.string.reduce_bright_colors_feature_name);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
-        state.icon = mIcon;
+        state.icon = ResourceIcon.get(state.value
+                ? drawable.qs_extra_dim_icon_on
+                : drawable.qs_extra_dim_icon_off);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 75fb393..f63f044 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -120,7 +120,9 @@
         state.value = isRecording || isStarting;
         state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_screen_record_label);
-        state.icon = ResourceIcon.get(R.drawable.ic_screenrecord);
+        state.icon = ResourceIcon.get(state.value
+                ? R.drawable.qs_screen_record_icon_on
+                : R.drawable.qs_screen_record_icon_off);
         // Show expand icon when clicking will open a dialog
         state.forceExpandIcon = state.state == Tile.STATE_INACTIVE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 2861ed7..24c4723 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -198,6 +198,8 @@
         mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW);
         mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
                 null);
+        mDialogView.setAccessibilityPaneTitle(
+                mContext.getText(R.string.accessibility_desc_quick_settings));
         final Window window = getWindow();
         window.setContentView(mDialogView);
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3429b9d..1011a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -33,6 +33,7 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
@@ -62,6 +63,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.Trace;
 import android.os.UserManager;
 import android.os.VibrationEffect;
@@ -236,6 +238,9 @@
     private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG_DRAWABLE = false;
 
+    private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
+            VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false);
+
     /**
      * The parallax amount of the quick settings translation when dragging down the panel
      */
@@ -682,14 +687,22 @@
 
     private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
         @Override
-        public void onDoubleTapRequired() {
+        public void onAdditionalTapRequired() {
             if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
                 mTapAgainViewController.show();
             } else {
                 mKeyguardIndicationController.showTransientIndication(
                         R.string.notification_tap_again);
             }
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+            if (!mStatusBarStateController.isDozing()) {
+                mVibratorHelper.vibrate(
+                        Process.myUid(),
+                        mView.getContext().getPackageName(),
+                        ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
+                        "falsing-additional-tap-required",
+                        TOUCH_VIBRATION_ATTRIBUTES);
+            }
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index abafecc..8d74a09 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -73,7 +73,7 @@
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
 
-    private GestureDetector mGestureDetector;
+    private GestureDetector mPulsingWakeupGestureHandler;
     private View mBrightnessMirror;
     private boolean mTouchActive;
     private boolean mTouchCancelled;
@@ -149,7 +149,8 @@
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
-        mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener);
+        mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
+                mPulsingGestureListener);
 
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
@@ -196,7 +197,7 @@
                 }
 
                 mFalsingCollector.onTouchEvent(ev);
-                mGestureDetector.onTouchEvent(ev);
+                mPulsingWakeupGestureHandler.onTouchEvent(ev);
                 mStatusBarKeyguardViewManager.onTouch(ev);
                 if (mBrightnessMirror != null
                         && mBrightnessMirror.getVisibility() == View.VISIBLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 9b3fe92..084b7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
@@ -36,12 +37,12 @@
 
 /**
  * If tap and/or double tap to wake is enabled, this gestureListener will wake the display on
- * tap/double tap when the device is pulsing (AoD 2). Taps are gated by the proximity sensor and
- * falsing manager.
+ * tap/double tap when the device is pulsing (AoD2) or transitioning to AoD. Taps are gated by the
+ * proximity sensor and falsing manager.
  *
- * Touches go through the [NotificationShadeWindowViewController] when the device is pulsing.
- * Otherwise, if the device is dozing and NOT pulsing, wake-ups are handled by
- * [com.android.systemui.doze.DozeSensors].
+ * Touches go through the [NotificationShadeWindowViewController] when the device is dozing but the
+ * screen is still ON and not in the true AoD display state. When the device is in the true AoD
+ * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors].
  */
 @CentralSurfacesComponent.CentralSurfacesScope
 class PulsingGestureListener @Inject constructor(
@@ -75,12 +76,12 @@
         dumpManager.registerDumpable(this)
     }
 
-    override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
-        if (statusBarStateController.isPulsing &&
+    override fun onSingleTapUp(e: MotionEvent): Boolean {
+        if (statusBarStateController.isDozing &&
                 singleTapEnabled &&
                 !dockManager.isDocked &&
                 !falsingManager.isProximityNear &&
-                !falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)
+                !falsingManager.isFalseTap(LOW_PENALTY)
         ) {
             centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
@@ -91,8 +92,15 @@
         return false
     }
 
-    override fun onDoubleTap(e: MotionEvent): Boolean {
-        if (statusBarStateController.isPulsing &&
+    /**
+     * Receives [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], and [MotionEvent.ACTION_UP]
+     * motion events for a double tap.
+     */
+    override fun onDoubleTapEvent(e: MotionEvent): Boolean {
+        // React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing
+        // checks MUST be on the ACTION_UP event.
+        if (e.actionMasked == MotionEvent.ACTION_UP &&
+                statusBarStateController.isDozing &&
                 (doubleTapEnabled || singleTapEnabled) &&
                 !falsingManager.isProximityNear &&
                 !falsingManager.isFalseDoubleTap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 867be1a..c070fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -40,7 +40,7 @@
 public class VibratorHelper {
 
     private final Vibrator mVibrator;
-    private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
+    public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
 
     private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT =
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 985f3cd..6649f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -667,9 +667,7 @@
     private void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
-            if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
-                reset(dozing /* hideBouncerWhenShowing */);
-            }
+            reset(true /* hideBouncerWhenShowing */);
             updateStates();
 
             if (!dozing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
deleted file mode 100644
index fe84674..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline
-
-import android.content.Context
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
-import javax.inject.Inject
-import javax.inject.Provider
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-
-/**
- * A temporary object that collects on [WifiViewModel] flows for debugging purposes.
- *
- * This will eventually get migrated to a view binder that will use the flow outputs to set state on
- * views. For now, this just collects on flows so that the information gets logged.
- */
-@SysUISingleton
-class ConnectivityInfoProcessor @Inject constructor(
-        context: Context,
-        // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's
-        // scope so we only do work when there's UI that cares about it.
-        @Application private val scope: CoroutineScope,
-        private val statusBarPipelineFlags: StatusBarPipelineFlags,
-        private val wifiViewModelProvider: Provider<WifiViewModel>,
-) : CoreStartable(context) {
-    override fun start() {
-        if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) {
-            return
-        }
-        // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can
-        // see the logs.
-        scope.launch {
-            wifiViewModelProvider.get().isActivityInVisible.collect { }
-        }
-    }
-}
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 681dc6f..9a7c3fa 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,25 +16,15 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
-import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
 import dagger.Binds
 import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
 
 @Module
 abstract class StatusBarPipelineModule {
-    /** Inject into ConnectivityInfoProcessor. */
-    @Binds
-    @IntoMap
-    @ClassKey(ConnectivityInfoProcessor::class)
-    abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
-
     @Binds
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index e7fa6d2..aae0f93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -27,7 +27,6 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -37,6 +36,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -57,9 +57,9 @@
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final DumpManager mDumpManager;
+    private final BluetoothLogger mLogger;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
@@ -70,6 +70,7 @@
     private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
 
     private boolean mEnabled;
+    @ConnectionState
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private boolean mAudioProfileOnly;
     private boolean mIsActive;
@@ -83,10 +84,12 @@
     public BluetoothControllerImpl(
             Context context,
             DumpManager dumpManager,
+            BluetoothLogger logger,
             @Background Looper bgLooper,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager) {
         mDumpManager = dumpManager;
+        mLogger = logger;
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
         mHandler = new H(mainLooper);
@@ -116,7 +119,7 @@
             return;
         }
         pw.print("  mEnabled="); pw.println(mEnabled);
-        pw.print("  mConnectionState="); pw.println(stateToString(mConnectionState));
+        pw.print("  mConnectionState="); pw.println(connectionStateToString(mConnectionState));
         pw.print("  mAudioProfileOnly="); pw.println(mAudioProfileOnly);
         pw.print("  mIsActive="); pw.println(mIsActive);
         pw.print("  mConnectedDevices="); pw.println(getConnectedDevices());
@@ -127,7 +130,7 @@
         }
     }
 
-    private static String stateToString(int state) {
+    private static String connectionStateToString(@ConnectionState int state) {
         switch (state) {
             case BluetoothAdapter.STATE_CONNECTED:
                 return "CONNECTED";
@@ -320,8 +323,8 @@
     }
 
     @Override
-    public void onBluetoothStateChanged(int bluetoothState) {
-        if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState));
+    public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
+        mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState));
         mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
                 || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
         mState = bluetoothState;
@@ -330,24 +333,25 @@
     }
 
     @Override
-    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress());
+    public void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {
+        mLogger.logDeviceAdded(cachedDevice.getAddress());
         cachedDevice.registerCallback(this);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
-    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress());
+    public void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {
+        mLogger.logDeviceDeleted(cachedDevice.getAddress());
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
-    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress());
+    public void onDeviceBondStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice, int bondState) {
+        mLogger.logBondStateChange(cachedDevice.getAddress(), bondState);
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -355,50 +359,47 @@
 
     @Override
     public void onDeviceAttributesChanged() {
-        if (DEBUG) Log.d(TAG, "DeviceAttributesChanged");
+        mLogger.logDeviceAttributesChanged();
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
-    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        if (DEBUG) {
-            Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " "
-                    + stateToString(state));
-        }
+    public void onConnectionStateChanged(
+            @Nullable CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state) {
+        mLogger.logDeviceConnectionStateChanged(
+                getAddressOrNull(cachedDevice), connectionStateToString(state));
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
-            int state, int bluetoothProfile) {
-        if (DEBUG) {
-            Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " "
-                    + stateToString(state) + " profileId=" + bluetoothProfile);
-        }
+    public void onProfileConnectionStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state,
+            int bluetoothProfile) {
+        mLogger.logProfileConnectionStateChanged(
+                cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile);
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
-        if (DEBUG) {
-            Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress()
-                    + " profileId=" + bluetoothProfile);
-        }
+    public void onActiveDeviceChanged(
+            @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+        mLogger.logActiveDeviceChanged(getAddressOrNull(activeDevice), bluetoothProfile);
         updateActive();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        if (DEBUG) {
-            Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " "
-                    + stateToString(state));
-        }
+    public void onAclConnectionStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice, int state) {
+        mLogger.logAclConnectionStateChanged(
+                cachedDevice.getAddress(), connectionStateToString(state));
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -415,6 +416,11 @@
         return state;
     }
 
+    @Nullable
+    private String getAddressOrNull(@Nullable CachedBluetoothDevice device) {
+        return device == null ? null : device.getAddress();
+    }
+
     @Override
     public void onServiceConnected() {
         updateConnected();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 3b8ed33..63e88a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1122,11 +1122,19 @@
         }
 
         public String getName(Context context, UserRecord item) {
+            return getName(context, item, false);
+        }
+
+        /**
+         * Returns the name for the given {@link UserRecord}.
+         */
+        public String getName(Context context, UserRecord item, boolean isTablet) {
             return LegacyUserUiHelper.getUserRecordName(
                     context,
                     item,
                     mController.isGuestUserAutoCreated(),
-                    mController.isGuestUserResetting());
+                    mController.isGuestUserResetting(),
+                    isTablet);
         }
 
         protected static ColorFilter getDisabledUserAvatarColorFilter() {
@@ -1136,8 +1144,12 @@
         }
 
         protected static Drawable getIconDrawable(Context context, UserRecord item) {
+            return getIconDrawable(context, item, false);
+        }
+        protected static Drawable getIconDrawable(Context context, UserRecord item,
+                boolean isTablet) {
             int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
-                    item.isAddUser, item.isGuest, item.isAddSupervisedUser);
+                    item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet);
             return context.getDrawable(iconRes);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index d43f739..5e2dde6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -173,8 +173,8 @@
             this,
             R.layout.user_switcher_fullscreen_popup_item,
             layoutInflater,
-            { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) },
-            { item: UserRecord -> adapter.findUserIcon(item).mutate().apply {
+            { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
+            { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply {
                 setTint(resources.getColor(
                     R.color.user_switcher_fullscreen_popup_item_tint,
                     getTheme()
@@ -322,6 +322,9 @@
         return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
     }
 
+    /**
+     * Provides views to populate the option menu.
+     */
     private class ItemAdapter(
         val parentContext: Context,
         val resource: Int,
@@ -375,20 +378,20 @@
             return view
         }
 
-        override fun getName(context: Context, item: UserRecord): String {
+        override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String {
             return if (item == manageUserRecord) {
                 getString(R.string.manage_users)
             } else {
-                super.getName(context, item)
+                super.getName(context, item, isTablet)
             }
         }
 
-        fun findUserIcon(item: UserRecord): Drawable {
+        fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable {
             if (item == manageUserRecord) {
                 return getDrawable(R.drawable.ic_manage_users)
             }
             if (item.info == null) {
-                return getIconDrawable(this@UserSwitcherActivity, item)
+                return getIconDrawable(this@UserSwitcherActivity, item, isTablet)
             }
             val userIcon = userManager.getUserIcon(item.info.id)
             if (userIcon != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index 18369d9..15fdc35 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -49,8 +49,11 @@
         isAddUser: Boolean,
         isGuest: Boolean,
         isAddSupervisedUser: Boolean,
+        isTablet: Boolean = false,
     ): Int {
-        return if (isAddUser) {
+        return if (isAddUser && isTablet) {
+            R.drawable.ic_account_circle_filled
+        } else if (isAddUser) {
             R.drawable.ic_add
         } else if (isGuest) {
             R.drawable.ic_account_circle
@@ -67,6 +70,7 @@
         record: UserRecord,
         isGuestUserAutoCreated: Boolean,
         isGuestUserResetting: Boolean,
+        isTablet: Boolean = false,
     ): String {
         val resourceId: Int? = getGuestUserRecordNameResourceId(record)
         return when {
@@ -80,6 +84,7 @@
                         isGuestUserResetting = isGuestUserResetting,
                         isAddUser = record.isAddUser,
                         isAddSupervisedUser = record.isAddSupervisedUser,
+                        isTablet = isTablet,
                     )
                 )
         }
@@ -108,12 +113,14 @@
         isGuestUserResetting: Boolean,
         isAddUser: Boolean,
         isAddSupervisedUser: Boolean,
+        isTablet: Boolean = false,
     ): Int {
         check(isGuest || isAddUser || isAddSupervisedUser)
 
         return when {
             isGuest && isGuestUserAutoCreated && isGuestUserResetting ->
                 com.android.settingslib.R.string.guest_resetting
+            isGuest && isTablet -> com.android.settingslib.R.string.guest_new_guest
             isGuest && isGuestUserAutoCreated -> com.android.internal.R.string.guest_name
             isGuest -> com.android.internal.R.string.guest_name
             isAddUser -> com.android.settingslib.R.string.user_add_user
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 83a3d0d..d7ad3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -129,7 +129,10 @@
                     viewModel.users.collect { users ->
                         val viewPool =
                             view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
-                        viewPool.forEach { view.removeView(it) }
+                        viewPool.forEach {
+                            view.removeView(it)
+                            flowWidget.removeView(it)
+                        }
                         users.forEach { userViewModel ->
                             val userView =
                                 if (viewPool.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c7ba518..903aba1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -48,6 +48,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -74,6 +75,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.text.InputFilter;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -1049,7 +1051,13 @@
                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
                 mMediaOutputDialogFactory.dismiss();
-                mVolumePanelFactory.create(true /* aboveStatusBar */, null);
+                if (FeatureFlagUtils.isEnabled(mContext,
+                        FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI)) {
+                    mVolumePanelFactory.create(true /* aboveStatusBar */, null);
+                } else {
+                    mActivityStarter.startActivity(new Intent(Settings.Panel.ACTION_VOLUME),
+                            true /* dismissShade */);
+                }
             });
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 914d945..25e7dbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -119,6 +119,20 @@
     }
 
     @Test
+    fun fontChanged_verifyFontSizeUpdated() {
+        clockEventController.clock = clock
+        verify(events).onColorPaletteChanged(any(), any(), any())
+
+        clockEventController.registerListeners()
+
+        val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+        verify(configurationController).addCallback(capture(captor))
+        captor.value.onDensityOrFontScaleChanged()
+
+        verify(events).onFontSettingChanged()
+    }
+
+    @Test
     fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
         clockEventController.clock = clock
         clockEventController.registerListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 508f81d..e3c7128 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -329,8 +329,7 @@
     @After
     public void tearDown() {
         mMockitoSession.finishMocking();
-        mKeyguardUpdateMonitor.removeCallback(mTestCallback);
-        mKeyguardUpdateMonitor.destroy();
+        cleanupKeyguardUpdateMonitor();
     }
 
     @Test
@@ -352,6 +351,7 @@
 
     @Test
     public void testSimStateInitialized() {
+        cleanupKeyguardUpdateMonitor();
         final int subId = 3;
         final int state = TelephonyManager.SIM_STATE_ABSENT;
 
@@ -1206,7 +1206,9 @@
 
     @Test
     public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
-        mFaceManager = null;
+        cleanupKeyguardUpdateMonitor();
+        mSpiedContext.addMockSystemService(FaceManager.class, null);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
@@ -1259,6 +1261,7 @@
 
     @Test
     public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
+        cleanupKeyguardUpdateMonitor();
         // This disables face auth
         when(mUserManager.isPrimaryUser()).thenReturn(false);
         mKeyguardUpdateMonitor =
@@ -1588,9 +1591,9 @@
 
     @Test
     public void testFingerAcquired_wakesUpPowerManager() {
-        mContext.getOrCreateTestableResources().addOverride(
+        cleanupKeyguardUpdateMonitor();
+        mSpiedContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.kg_wake_on_acquire_start, true);
-        mSpiedContext = spy(mContext);
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
         fingerprintAcquireStart();
 
@@ -1599,15 +1602,23 @@
 
     @Test
     public void testFingerAcquired_doesNotWakeUpPowerManager() {
-        mContext.getOrCreateTestableResources().addOverride(
+        cleanupKeyguardUpdateMonitor();
+        mSpiedContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.kg_wake_on_acquire_start, false);
-        mSpiedContext = spy(mContext);
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
         fingerprintAcquireStart();
 
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
     }
 
+    private void cleanupKeyguardUpdateMonitor() {
+        if (mKeyguardUpdateMonitor != null) {
+            mKeyguardUpdateMonitor.removeCallback(mTestCallback);
+            mKeyguardUpdateMonitor.destroy();
+            mKeyguardUpdateMonitor = null;
+        }
+    }
+
     private void faceAuthLockedOut() {
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback
                 .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index bf3788e..4a5b23c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -29,6 +29,7 @@
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
+import android.view.KeyEvent
 import android.view.View
 import android.view.WindowInsets
 import android.view.WindowManager
@@ -92,6 +93,21 @@
     }
 
     @Test
+    fun testDismissesOnBack() {
+        val container = initializeFingerprintContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testIgnoresAnimatedInWhenDismissed() {
         val container = initializeFingerprintContainer(addToView = false)
         container.dismissFromSystemServer()
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 44ef922..37bb0c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Point
 import android.hardware.biometrics.BiometricSourceType
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
@@ -76,6 +77,7 @@
     @Mock private lateinit var udfpsController: UdfpsController
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var lightRevealScrim: LightRevealScrim
+    @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
 
     @Before
     fun setUp() {
@@ -86,6 +88,7 @@
                 .startMocking()
 
         `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE)
+        `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp))
         `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
 
         controller = AuthRippleController(
@@ -132,7 +135,7 @@
             false /* isStrongBiometric */)
 
         // THEN update sensor location and show ripple
-        verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f)
+        verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
         verify(rippleView).startUnlockedRipple(any())
     }
 
@@ -155,7 +158,7 @@
                 false /* isStrongBiometric */)
 
         // THEN update sensor location and show ripple
-        verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f)
+        verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
         verify(rippleView).startUnlockedRipple(any())
     }
 
@@ -342,4 +345,23 @@
         captor.value.onUiModeChanged()
         verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
     }
+
+    @Test
+    fun testUdfps_onFingerDown_showDwellRipple() {
+        // GIVEN view is already attached
+        controller.onViewAttached()
+        val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
+        verify(udfpsController).addCallback(captor.capture())
+
+        // GIVEN fp is updated to Point(5, 5)
+        val fpsLocation = Point(5, 5)
+        `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
+
+        // WHEN finger is down
+        captor.value.onFingerDown()
+
+        // THEN update sensor location and show ripple
+        verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
+        verify(rippleView).startDwellRipple(false)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index cb8358d..5c564e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -17,23 +17,13 @@
 package com.android.systemui.biometrics
 
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
-import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
+import android.hardware.biometrics.BiometricOverlayConstants.*
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.View
-import android.view.Surface
+import android.view.*
 import android.view.Surface.Rotation
-import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -65,7 +55,6 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
 
-private const val HAL_CONTROLS_ILLUMINATION = true
 private const val REQUEST_ID = 2L
 
 // Dimensions for the current display resolution.
@@ -95,8 +84,9 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var systemClock: SystemClock
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
-    @Mock private lateinit var hbmProvider: UdfpsHbmProvider
+    @Mock private lateinit var unlockedScreenOffAnimationController:
+            UnlockedScreenOffAnimationController
+    @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
     @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
     @Mock private lateinit var udfpsController: UdfpsController
     @Mock private lateinit var udfpsView: UdfpsView
@@ -130,8 +120,9 @@
             statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, systemClock, keyguardStateController,
-            unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider,
-            REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator)
+            unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
+            controllerCallback, onTouch, activityLaunchAnimator
+        )
         block()
     }
 
@@ -246,7 +237,7 @@
         val didShow = controllerOverlay.show(udfpsController, overlayParams)
 
         verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
-        verify(udfpsView).setHbmProvider(eq(hbmProvider))
+        verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode))
         verify(udfpsView).animationViewController = any()
         verify(udfpsView).addView(any())
 
@@ -351,12 +342,12 @@
     }
 
     @Test
-    fun stopIlluminatingOnHide() = withReason(REASON_AUTH_BP) {
-        whenever(udfpsView.isIlluminationRequested).thenReturn(true)
+    fun unconfigureDisplayOnHide() = withReason(REASON_AUTH_BP) {
+        whenever(udfpsView.isDisplayConfigured).thenReturn(true)
 
         controllerOverlay.show(udfpsController, overlayParams)
         controllerOverlay.hide()
-        verify(udfpsView).stopIllumination()
+        verify(udfpsView).unconfigureDisplay()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index d7a0a0a..a6c0539 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -125,7 +125,7 @@
     @Mock
     private WindowManager mWindowManager;
     @Mock
-    private UdfpsHbmProvider mHbmProvider;
+    private UdfpsDisplayModeProvider mDisplayModeProvider;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
@@ -193,7 +193,7 @@
     private IUdfpsOverlayController mOverlayController;
     @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
     @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
-    @Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor;
+    @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
     @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
     private ScreenLifecycle.Observer mScreenObserver;
 
@@ -256,7 +256,7 @@
                 mVibrator,
                 mUdfpsHapticsSimulator,
                 mUdfpsShell,
-                Optional.of(mHbmProvider),
+                Optional.of(mDisplayModeProvider),
                 mKeyguardStateController,
                 mDisplayManager,
                 mHandler,
@@ -512,7 +512,7 @@
         final float expectedMajor = touchMajor / scaleFactor;
 
         // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // Show the overlay.
@@ -590,7 +590,7 @@
     @Test
     public void fingerDown() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
@@ -617,12 +617,12 @@
         verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
                 anyFloat(), anyFloat());
         verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
-        // AND illumination begins
-        verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
+        // AND display configuration begins
+        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
         verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        // AND onIlluminatedRunnable notifies FingerprintManager about onUiReady
-        mOnIlluminatedRunnableCaptor.getValue().run();
+        // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+        mOnDisplayConfiguredCaptor.getValue().run();
         mBiometricsExecutor.runAllReady();
         InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
         inOrder.verify(mAlternateTouchProvider).onUiReady();
@@ -640,10 +640,10 @@
         // WHEN fingerprint is requested because of AOD interrupt
         mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
         mFgExecutor.runAllReady();
-        // THEN illumination begins
-        // AND onIlluminatedRunnable that notifies FingerprintManager is set
-        verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
-        mOnIlluminatedRunnableCaptor.getValue().run();
+        // THEN display configuration begins
+        // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        mOnDisplayConfiguredCaptor.getValue().run();
         mBiometricsExecutor.runAllReady();
         verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
                 eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -661,11 +661,11 @@
         mFgExecutor.runAllReady();
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
-        when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
         // WHEN it is cancelled
         mUdfpsController.onCancelUdfps();
-        // THEN the illumination is hidden
-        verify(mUdfpsView).stopIllumination();
+        // THEN the display is unconfigured
+        verify(mUdfpsView).unconfigureDisplay();
     }
 
     @Test
@@ -678,12 +678,12 @@
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
-        when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
-        // THEN the illumination is hidden
-        verify(mUdfpsView).stopIllumination();
+        // THEN the display is unconfigured
+        verify(mUdfpsView).unconfigureDisplay();
     }
 
     @Test
@@ -698,8 +698,8 @@
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
 
-        // THEN no illumination because screen is off
-        verify(mUdfpsView, never()).startIllumination(any());
+        // THEN display doesn't get configured because it's off
+        verify(mUdfpsView, never()).configureDisplay(any());
     }
 
     @Test
@@ -715,14 +715,14 @@
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
 
-        // THEN no illumination because screen is off
-        verify(mUdfpsView, never()).startIllumination(any());
+        // THEN display doesn't get configured because it's off
+        verify(mUdfpsView, never()).configureDisplay(any());
     }
 
     @Test
     public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // GIVEN that the overlay is showing and a11y touch exploration enabled
@@ -757,7 +757,7 @@
     @Test
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 0327cfc..b78c063 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -36,13 +36,12 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private const val SENSOR_X = 50
 private const val SENSOR_Y = 250
@@ -57,7 +56,7 @@
     var rule = MockitoJUnit.rule()
 
     @Mock
-    lateinit var hbmProvider: UdfpsHbmProvider
+    lateinit var hbmProvider: UdfpsDisplayModeProvider
     @Mock
     lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView>
 
@@ -66,13 +65,11 @@
     @Before
     fun setup() {
         context.setTheme(R.style.Theme_AppCompat)
-        context.orCreateTestableResources.addOverride(
-            com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0)
         view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
         view.animationViewController = animationViewController
         val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect
         view.overlayParams = UdfpsOverlayParams(sensorBounds, 1920, 1080, 1f, Surface.ROTATION_0)
-        view.setHbmProvider(hbmProvider)
+        view.setUdfpsDisplayModeProvider(hbmProvider)
         ViewUtils.attachView(view)
     }
 
@@ -143,27 +140,27 @@
     @Test
     fun startAndStopIllumination() {
         val onDone: Runnable = mock()
-        view.startIllumination(onDone)
+        view.configureDisplay(onDone)
 
         val illuminator = withArgCaptor<Runnable> {
-            verify(hbmProvider).enableHbm(anyBoolean(), capture())
+            verify(hbmProvider).enable(capture())
         }
 
-        assertThat(view.isIlluminationRequested).isTrue()
-        verify(animationViewController).onIlluminationStarting()
-        verify(animationViewController, never()).onIlluminationStopped()
+        assertThat(view.isDisplayConfigured).isTrue()
+        verify(animationViewController).onDisplayConfiguring()
+        verify(animationViewController, never()).onDisplayUnconfigured()
         verify(onDone, never()).run()
 
         // fake illumination event
         illuminator.run()
         waitForLooper()
         verify(onDone).run()
-        verify(hbmProvider, never()).disableHbm(any())
+        verify(hbmProvider, never()).disable(any())
 
-        view.stopIllumination()
-        assertThat(view.isIlluminationRequested).isFalse()
-        verify(animationViewController).onIlluminationStopped()
-        verify(hbmProvider).disableHbm(nullable(Runnable::class.java))
+        view.unconfigureDisplay()
+        assertThat(view.isDisplayConfigured).isFalse()
+        verify(animationViewController).onDisplayUnconfigured()
+        verify(hbmProvider).disable(nullable(Runnable::class.java))
     }
 
     private fun waitForLooper() = TestableLooper.get(this).processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index acb5622c..3e9cf1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -229,7 +229,10 @@
     }
 
     @Test
-    public void testAvoidDozingNotPulsing() {
+    public void testGestureWhenDozing() {
+        // We check the FalsingManager for taps during the transition to AoD (dozing=true,
+        // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur
+        // while the device is dozing.
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
 
@@ -239,13 +242,13 @@
         mFalsingCollector.onTouchEvent(down);
         verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
 
-        // Up event would normally flush the up event, but doesn't.
+        // Up event flushes
         mFalsingCollector.onTouchEvent(up);
-        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+        verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class));
     }
 
     @Test
-    public void testAvoidDozingButPulsing() {
+    public void testGestureWhenPulsing() {
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
new file mode 100644
index 0000000..08fe7c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IntentCreatorTest extends SysuiTestCase {
+    private static final int EXTERNAL_INTENT_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK
+            | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+    @Test
+    public void test_getTextEditorIntent() {
+        Intent intent = IntentCreator.getTextEditorIntent(getContext());
+        assertEquals(new ComponentName(getContext(), EditTextActivity.class),
+                intent.getComponent());
+        assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+    }
+
+    @Test
+    public void test_getRemoteCopyIntent() {
+        getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage,
+                "");
+
+        ClipData clipData = ClipData.newPlainText("Test", "Test Item");
+        Intent intent = IntentCreator.getRemoteCopyIntent(clipData, getContext());
+
+        assertEquals(null, intent.getComponent());
+        assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+        assertEquals(clipData, intent.getClipData());
+
+        // Try again with a remote copy component
+        ComponentName fakeComponent = new ComponentName("com.android.remotecopy",
+                "com.android.remotecopy.RemoteCopyActivity");
+        getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage,
+                fakeComponent.flattenToString());
+
+        intent = IntentCreator.getRemoteCopyIntent(clipData, getContext());
+        assertEquals(fakeComponent, intent.getComponent());
+    }
+
+    @Test
+    public void test_getImageEditIntent() {
+        getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
+                "");
+        Uri fakeUri = Uri.parse("content://foo");
+        Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext());
+
+        assertEquals(Intent.ACTION_EDIT, intent.getAction());
+        assertEquals("image/*", intent.getType());
+        assertEquals(null, intent.getComponent());
+        assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+
+        // try again with an editor component
+        ComponentName fakeComponent = new ComponentName("com.android.remotecopy",
+                "com.android.remotecopy.RemoteCopyActivity");
+        getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
+                fakeComponent.flattenToString());
+        intent = IntentCreator.getImageEditIntent(fakeUri, getContext());
+        assertEquals(fakeComponent, intent.getComponent());
+    }
+
+    @Test
+    public void test_getShareIntent_plaintext() {
+        ClipData clipData = ClipData.newPlainText("Test", "Test Item");
+        Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+
+        assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
+        assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+        Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+        assertEquals("Test Item", target.getStringExtra(Intent.EXTRA_TEXT));
+        assertEquals("text/plain", target.getType());
+    }
+
+    @Test
+    public void test_getShareIntent_html() {
+        ClipData clipData = ClipData.newHtmlText("Test", "Some HTML",
+                "<b>Some HTML</b>");
+        Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+
+        assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
+        assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+        Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+        assertEquals("Some HTML", target.getStringExtra(Intent.EXTRA_TEXT));
+        assertEquals("text/plain", target.getType());
+    }
+
+    @Test
+    public void test_getShareIntent_image() {
+        Uri uri = Uri.parse("content://something");
+        ClipData clipData = new ClipData("Test", new String[]{"image/png"},
+                new ClipData.Item(uri));
+        Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+
+        assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
+        assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+        Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+        assertEquals(uri, target.getData());
+        assertEquals("image/png", target.getType());
+    }
+
+    // Assert that the given flags are set
+    private void assertFlags(Intent intent, int flags) {
+        assertTrue((intent.getFlags() & flags) == flags);
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index bef4695..7de5719 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -591,14 +591,20 @@
 
     @Test
     fun bindAlbumView_bitmapInLaterStates_setAfterExecutors() {
-        val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
-        val canvas = Canvas(bmp)
-        canvas.drawColor(Color.RED)
-        val albumArt = Icon.createWithBitmap(bmp)
+        val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+        val redCanvas = Canvas(redBmp)
+        redCanvas.drawColor(Color.RED)
+        val redArt = Icon.createWithBitmap(redBmp)
+
+        val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+        val greenCanvas = Canvas(greenBmp)
+        greenCanvas.drawColor(Color.GREEN)
+        val greenArt = Icon.createWithBitmap(greenBmp)
 
         val state0 = mediaData.copy(artwork = null)
-        val state1 = mediaData.copy(artwork = albumArt)
-        val state2 = mediaData.copy(artwork = albumArt)
+        val state1 = mediaData.copy(artwork = redArt)
+        val state2 = mediaData.copy(artwork = redArt)
+        val state3 = mediaData.copy(artwork = greenArt)
         player.attachPlayer(viewHolder)
 
         // First binding sets (empty) drawable
@@ -627,6 +633,12 @@
         bgExecutor.runAllReady()
         mainExecutor.runAllReady()
         verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java))
+
+        // Fourth binding to new image runs transition due to color scheme change
+        player.bindPlayer(state3, PACKAGE)
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
+        verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index bf682a8..3fd2501 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -33,12 +33,15 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
@@ -54,6 +57,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class ColorInversionTileTest extends SysuiTestCase {
+    private static final Integer COLOR_INVERSION_DISABLED = 0;
+    private static final Integer COLOR_INVERSION_ENABLED = 1;
 
     @Mock
     private QSTileHost mHost;
@@ -113,4 +118,24 @@
         assertThat(IntentCaptor.getValue().getAction()).isEqualTo(
                 Settings.ACTION_COLOR_INVERSION_SETTINGS);
     }
+
+    @Test
+    public void testIcon_whenColorInversionDisabled_isOffState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off));
+    }
+
+    @Test
+    public void testIcon_whenColorInversionEnabled_isOnState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
new file mode 100644
index 0000000..ce62f2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.policy.DataSaverController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class DataSaverTileTest : SysuiTestCase() {
+
+    @Mock private lateinit var mHost: QSHost
+    @Mock private lateinit var mMetricsLogger: MetricsLogger
+    @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+    @Mock private lateinit var mActivityStarter: ActivityStarter
+    @Mock private lateinit var mQsLogger: QSLogger
+    private val falsingManager = FalsingManagerFake()
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var dataSaverController: DataSaverController
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
+    private val uiEventLogger = UiEventLoggerFake()
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var tile: DataSaverTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        Mockito.`when`(mHost.context).thenReturn(mContext)
+        Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger)
+
+        tile =
+            DataSaverTile(
+                mHost,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                falsingManager,
+                mMetricsLogger,
+                statusBarStateController,
+                activityStarter,
+                mQsLogger,
+                dataSaverController,
+                dialogLaunchAnimator
+            )
+    }
+
+    @Test
+    fun testIcon_whenDataSaverEnabled_isOnState() {
+        val state = QSTile.BooleanState()
+
+        tile.handleUpdateState(state, true)
+
+        assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on))
+    }
+
+    @Test
+    fun testIcon_whenDataSaverDisabled_isOffState() {
+        val state = QSTile.BooleanState()
+
+        tile.handleUpdateState(state, false)
+
+        assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
new file mode 100644
index 0000000..d0f851b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -0,0 +1,108 @@
+package com.android.systemui.qs.tiles
+
+import android.content.Context
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class FlashlightTileTest : SysuiTestCase() {
+
+    @Mock private lateinit var mockContext: Context
+
+    @Mock private lateinit var qsLogger: QSLogger
+
+    @Mock private lateinit var qsHost: QSTileHost
+
+    @Mock private lateinit var metricsLogger: MetricsLogger
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Mock private lateinit var flashlightController: FlashlightController
+
+    private val uiEventLogger = UiEventLoggerFake()
+    private val falsingManager = FalsingManagerFake()
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var tile: FlashlightTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        Mockito.`when`(qsHost.context).thenReturn(mockContext)
+        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+        tile =
+            FlashlightTile(
+                qsHost,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                falsingManager,
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                flashlightController
+            )
+    }
+
+    @Test
+    fun testIcon_whenFlashlightEnabled_isOnState() {
+        Mockito.`when`(flashlightController.isAvailable).thenReturn(true)
+        Mockito.`when`(flashlightController.isEnabled).thenReturn(true)
+        val state = QSTile.BooleanState()
+
+        tile.handleUpdateState(state, /* arg= */ null)
+
+        Truth.assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_on))
+    }
+
+    @Test
+    fun testIcon_whenFlashlightDisabled_isOffState() {
+        Mockito.`when`(flashlightController.isAvailable).thenReturn(true)
+        Mockito.`when`(flashlightController.isEnabled).thenReturn(false)
+        val state = QSTile.BooleanState()
+
+        tile.handleUpdateState(state, /* arg= */ null)
+
+        Truth.assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+    }
+
+    @Test
+    fun testIcon_whenFlashlightUnavailable_isOffState() {
+        Mockito.`when`(flashlightController.isAvailable).thenReturn(false)
+        val state = QSTile.BooleanState()
+
+        tile.handleUpdateState(state, /* arg= */ null)
+
+        Truth.assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
new file mode 100644
index 0000000..188c3a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.qs.tiles
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.policy.LocationController
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NightDisplayTileTest : SysuiTestCase() {
+    @Mock
+    private lateinit var mHost: QSHost
+
+    @Mock
+    private lateinit var mMetricsLogger: MetricsLogger
+
+    @Mock
+    private lateinit var mStatusBarStateController: StatusBarStateController
+
+    @Mock
+    private lateinit var mActivityStarter: ActivityStarter
+
+    @Mock
+    private lateinit var mQsLogger: QSLogger
+
+    @Mock
+    private lateinit var mLocationController: LocationController
+
+    @Mock
+    private lateinit var mColorDisplayManager: ColorDisplayManager
+
+    @Mock
+    private lateinit var mNightDisplayListenerBuilder: NightDisplayListenerModule.Builder
+
+    @Mock
+    private lateinit var mNightDisplayListener: NightDisplayListener
+
+    private lateinit var mTestableLooper: TestableLooper
+    private lateinit var mTile: NightDisplayTile
+
+    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mTestableLooper = TestableLooper.get(this)
+        whenever(mHost.context).thenReturn(mContext)
+        whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger)
+        whenever(mHost.userContext).thenReturn(mContext)
+        whenever(mNightDisplayListenerBuilder.setUser(anyInt())).thenReturn(
+            mNightDisplayListenerBuilder
+        )
+        whenever(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener)
+
+        mTile = NightDisplayTile(
+            mHost,
+            mTestableLooper.looper,
+            Handler(mTestableLooper.looper),
+            FalsingManagerFake(),
+            mMetricsLogger,
+            mStatusBarStateController,
+            mActivityStarter,
+            mQsLogger,
+            mLocationController,
+            mColorDisplayManager,
+            mNightDisplayListenerBuilder
+        )
+    }
+
+    @Test
+    fun testIcon_whenDisabled_showsOffState() {
+        whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(false)
+        val state = QSTile.BooleanState()
+
+        mTile.handleUpdateState(state, /* arg= */ null)
+
+        Truth.assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off))
+    }
+
+    @Test
+    fun testIcon_whenEnabled_showsOnState() {
+        whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(true)
+        val state = QSTile.BooleanState()
+
+        mTile.handleUpdateState(state, /* arg= */ null)
+
+        Truth.assertThat(state.icon)
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 9eb688d..8601d6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -32,13 +32,16 @@
 
 import com.android.internal.R;
 import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R.drawable;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
@@ -130,4 +133,26 @@
                 .setReduceBrightColorsActivated(eq(true));
     }
 
+    @Test
+    public void testIcon_whenTileEnabled_isOnState() {
+        when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(true);
+        mTile.refreshState();
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_on));
+    }
+
+    @Test
+    public void testIcon_whenTileDisabled_isOffState() {
+        when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false);
+        mTile.refreshState();
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_off));
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index e6bd396..30debdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -41,9 +41,11 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -229,4 +231,38 @@
 
         assertFalse(mTile.getState().forceExpandIcon);
     }
+
+    @Test
+    public void testIcon_whenRecording_isOnState() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(true);
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+    }
+
+    @Test
+    public void testIcon_whenStarting_isOnState() {
+        when(mController.isStarting()).thenReturn(true);
+        when(mController.isRecording()).thenReturn(false);
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+    }
+
+    @Test
+    public void testIcon_whenRecordingOff_isOffState() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(false);
+        QSTile.BooleanState state = new QSTile.BooleanState();
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index d09a5a1..f922475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -143,6 +143,12 @@
     }
 
     @Test
+    public void createInternetDialog_setAccessibilityPaneTitleToQuickSettings() {
+        assertThat(mDialogView.getAccessibilityPaneTitle())
+                .isEqualTo(mContext.getText(R.string.accessibility_desc_quick_settings));
+    }
+
+    @Test
     public void hideWifiViews_WifiViewsGone() {
         mInternetDialog.hideWifiViews();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 6ce9cff..002ef29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -34,6 +34,7 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
+import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.util.ScreenshotHelper
 import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
@@ -65,6 +66,7 @@
 private const val TASK_ID = 1
 
 @RunWith(AndroidTestingRunner::class)
+@SmallTest
 class TakeScreenshotServiceTest : SysuiTestCase() {
 
     private val application = mock<Application>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e730713..b40d5ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1022,7 +1022,7 @@
         FalsingManager.FalsingTapListener listener = getFalsingTapListener();
         mStatusBarStateController.setState(KEYGUARD);
 
-        listener.onDoubleTapRequired();
+        listener.onAdditionalTapRequired();
 
         verify(mKeyguardIndicationController).showTransientIndication(anyInt());
     }
@@ -1032,7 +1032,7 @@
         FalsingManager.FalsingTapListener listener = getFalsingTapListener();
         mStatusBarStateController.setState(SHADE_LOCKED);
 
-        listener.onDoubleTapRequired();
+        listener.onAdditionalTapRequired();
 
         verify(mTapAgainViewController).show();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 97c0bb2..09add65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -89,7 +89,7 @@
 
     @Test
     fun testGestureDetector_singleTapEnabled() {
-        whenever(statusBarStateController.isPulsing).thenReturn(true)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
 
         // GIVEN tap is enabled, prox not covered
         whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true)
@@ -100,7 +100,7 @@
         whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
 
         // WHEN there's a tap
-        underTest.onSingleTapConfirmed(downEv)
+        underTest.onSingleTapUp(upEv)
 
         // THEN wake up device if dozing
         verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
@@ -108,7 +108,7 @@
 
     @Test
     fun testGestureDetector_doubleTapEnabled() {
-        whenever(statusBarStateController.isPulsing).thenReturn(true)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
 
         // GIVEN double tap is enabled, prox not covered
         whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true)
@@ -119,15 +119,27 @@
         whenever(falsingManager.isFalseDoubleTap).thenReturn(false)
 
         // WHEN there's a double tap
-        underTest.onDoubleTap(downEv)
+        underTest.onDoubleTapEvent(upEv)
 
         // THEN wake up device if dozing
         verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
     }
 
     @Test
+    fun testGestureDetector_doubleTapEnabled_onDownEvent_noFalsingCheck() {
+        // GIVEN tap is enabled
+        whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true)
+
+        // WHEN there's a double tap on DOWN event
+        underTest.onDoubleTapEvent(downEv)
+
+        // THEN don't check the falsing manager, should only be checked on the UP event
+        verify(falsingManager, never()).isFalseDoubleTap()
+    }
+
+    @Test
     fun testGestureDetector_singleTapEnabled_falsing() {
-        whenever(statusBarStateController.isPulsing).thenReturn(true)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
 
         // GIVEN tap is enabled, prox not covered
         whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true)
@@ -138,29 +150,43 @@
         whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
 
         // WHEN there's a tap
-        underTest.onSingleTapConfirmed(downEv)
+        underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
         verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
     }
 
     @Test
-    fun testGestureDetector_notPulsing_noFalsingCheck() {
-        whenever(statusBarStateController.isPulsing).thenReturn(false)
+    fun testSingleTap_notDozing_noFalsingCheck() {
+        whenever(statusBarStateController.isDozing).thenReturn(false)
 
-        // GIVEN tap is enabled, prox not covered
+        // GIVEN tap is enabled
         whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true)
         // WHEN there's a tap
-        underTest.onSingleTapConfirmed(downEv)
+        underTest.onSingleTapUp(upEv)
 
-        // THEN the falsing manager never gets a call (because the device wasn't pulsing
+        // THEN the falsing manager never gets a call (because the device wasn't dozing
+        // during the tap)
+        verify(falsingManager, never()).isFalseTap(anyInt())
+    }
+
+    @Test
+    fun testDoubleTap_notDozing_noFalsingCheck() {
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        // GIVEN tap is enabled
+        whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true)
+        // WHEN there's a tap
+        underTest.onDoubleTapEvent(upEv)
+
+        // THEN the falsing manager never gets a call (because the device wasn't dozing
         // during the tap)
         verify(falsingManager, never()).isFalseTap(anyInt())
     }
 
     @Test
     fun testGestureDetector_doubleTapEnabled_falsing() {
-        whenever(statusBarStateController.isPulsing).thenReturn(true)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
 
         // GIVEN double tap is enabled, prox not covered
         whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true)
@@ -170,8 +196,8 @@
         // GIVEN the falsing manager thinks the tap is a false tap
         whenever(falsingManager.isFalseDoubleTap).thenReturn(true)
 
-        // WHEN there's a tap
-        underTest.onDoubleTap(downEv)
+        // WHEN there's a double tap ACTION_UP event
+        underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
         verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
@@ -179,7 +205,7 @@
 
     @Test
     fun testGestureDetector_singleTapEnabled_proxCovered() {
-        whenever(statusBarStateController.isPulsing).thenReturn(true)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
 
         // GIVEN tap is enabled, not a false tap based on classifiers
         whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true)
@@ -190,7 +216,7 @@
         whenever(falsingManager.isProximityNear()).thenReturn(true)
 
         // WHEN there's a tap
-        underTest.onSingleTapConfirmed(downEv)
+        underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
         verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
@@ -198,7 +224,7 @@
 
     @Test
     fun testGestureDetector_doubleTapEnabled_proxCovered() {
-        whenever(statusBarStateController.isPulsing).thenReturn(true)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
 
         // GIVEN double tap is enabled, not a false tap based on classifiers
         whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true)
@@ -209,7 +235,7 @@
         whenever(falsingManager.isProximityNear()).thenReturn(true)
 
         // WHEN there's a tap
-        underTest.onDoubleTap(downEv)
+        underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
         verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
@@ -227,3 +253,4 @@
 }
 
 private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
new file mode 100644
index 0000000..eb34561
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.TextAnimator
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class AnimatableClockViewTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    @Mock private lateinit var mockTextAnimator: TextAnimator
+    private lateinit var clockView: AnimatableClockView
+
+    @Before
+    fun setUp() {
+        val layoutInflater = LayoutInflater.from(context)
+        clockView =
+            layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
+        clockView.textAnimatorFactory = { _, _ -> mockTextAnimator }
+    }
+
+    @Test
+    fun validateColorAnimationRunsBeforeMeasure() {
+        clockView.setColors(100, 200)
+        clockView.animateAppearOnLockscreen()
+        clockView.measure(50, 50)
+
+        verify(mockTextAnimator).glyphFilter = null
+        verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
+        verifyNoMoreInteractions(mockTextAnimator)
+    }
+
+    @Test
+    fun validateColorAnimationRunsAfterMeasure() {
+        clockView.setColors(100, 200)
+        clockView.measure(50, 50)
+        clockView.animateAppearOnLockscreen()
+
+        verify(mockTextAnimator, times(2)).glyphFilter = null
+        verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
+        verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
+        verifyNoMoreInteractions(mockTextAnimator)
+    }
+}
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 e790d85..a4453f8 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
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -505,4 +506,21 @@
         mBouncerExpansionCallback.onVisibilityChanged(false);
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
     }
+
+    @Test
+    public void testSetDozing_Dozing() {
+        clearInvocations(mBouncer);
+        mStatusBarKeyguardViewManager.onDozingChanged(true);
+        // Once when shown and once with dozing changed.
+        verify(mBouncer, times(1)).hide(false);
+    }
+
+    @Test
+    public void testSetDozing_notDozing() {
+        mStatusBarKeyguardViewManager.onDozingChanged(true);
+        clearInvocations(mBouncer);
+        mStatusBarKeyguardViewManager.onDozingChanged(false);
+        // Once when shown and twice with dozing changed.
+        verify(mBouncer, times(1)).hide(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 3dd36d1..d0391ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -41,6 +41,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
@@ -81,6 +82,7 @@
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
                 mMockDumpManager,
+                mock(BluetoothLogger.class),
                 mTestableLooper.getLooper(),
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager);
@@ -233,4 +235,11 @@
         assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive());
         assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
     }
+
+    /** Regression test for b/246876230. */
+    @Test
+    public void testOnActiveDeviceChanged_null_noCrash() {
+        mBluetoothControllerImpl.onActiveDeviceChanged(null, BluetoothProfile.HEADSET);
+        // No assert, just need no crash.
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f55cfb1..8d94f95 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1113,7 +1113,8 @@
             final List<AccessibilityServiceInfo> result = new ArrayList<>(serviceCount);
             for (int i = 0; i < serviceCount; ++i) {
                 final AccessibilityServiceConnection service = services.get(i);
-                if ((service.mFeedbackType & feedbackType) != 0) {
+                if ((service.mFeedbackType & feedbackType) != 0
+                        || feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) {
                     result.add(service.getServiceInfo());
                 }
             }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 93cc611..82628a6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -46,6 +46,9 @@
     private static final String TAG = "CDM_CompanionServiceConnector";
     private static final boolean DEBUG = false;
 
+    /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
+    private static final long UNBIND_POST_DELAY_MS = 5_000;
+
     /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector}  */
     interface Listener {
         void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
@@ -110,9 +113,15 @@
      * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly,
      * because the latter may cause previously posted callback, such as
      * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped.
+     *
+     * {@link ICompanionDeviceService} is a non-blocking interface and doesn't wait for job
+     * completion, which makes {@link ServiceConnector#post(VoidJob)} obsolete for ensuring the
+     * order of execution. Give 5 seconds for all the callbacks to finish before unbinding. They
+     * may or may not have finished executing, but we shouldn't let user-overridden methods block
+     * the service from unbinding indefinitely.
      */
     void postUnbind() {
-        post(it -> unbind());
+        getJobHandler().postDelayed(this::unbind, UNBIND_POST_DELAY_MS);
     }
 
     boolean isPrimary() {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 05e85e3..838cbd9 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -61,10 +61,12 @@
 
     private static final AtomicLong sNextPhysId = new AtomicLong(1);
 
+    static final String PHYS_TYPE_DPAD = "Dpad";
     static final String PHYS_TYPE_KEYBOARD = "Keyboard";
     static final String PHYS_TYPE_MOUSE = "Mouse";
     static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
     @StringDef(prefix = { "PHYS_TYPE_" }, value = {
+            PHYS_TYPE_DPAD,
             PHYS_TYPE_KEYBOARD,
             PHYS_TYPE_MOUSE,
             PHYS_TYPE_TOUCHSCREEN,
@@ -121,6 +123,22 @@
         }
     }
 
+    void createDpad(@NonNull String deviceName,
+                        int vendorId,
+                        int productId,
+                        @NonNull IBinder deviceToken,
+                        int displayId) {
+        final String phys = createPhys(PHYS_TYPE_DPAD);
+        try {
+            createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId,
+                    productId, deviceToken, displayId, phys,
+                    () -> mNativeWrapper.openUinputDpad(deviceName, vendorId, productId, phys));
+        } catch (DeviceCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual dpad device '" + deviceName + "'.", e);
+        }
+    }
+
     void createKeyboard(@NonNull String deviceName,
             int vendorId,
             int productId,
@@ -253,6 +271,19 @@
         InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
     }
 
+    boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
+        synchronized (mLock) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
+                throw new IllegalArgumentException(
+                        "Could not send key event to input device for given token");
+            }
+            return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getFileDescriptor(),
+                    event.getKeyCode(), event.getAction());
+        }
+    }
+
     boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
         synchronized (mLock) {
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
@@ -366,6 +397,8 @@
         }
     }
 
+    private static native int nativeOpenUinputDpad(String deviceName, int vendorId,
+            int productId, String phys);
     private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
             int productId, String phys);
     private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
@@ -373,6 +406,7 @@
     private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
             int productId, String phys, int height, int width);
     private static native boolean nativeCloseUinput(int fd);
+    private static native boolean nativeWriteDpadKeyEvent(int fd, int androidKeyCode, int action);
     private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
     private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
     private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType,
@@ -385,6 +419,10 @@
     /** Wrapper around the static native methods for tests. */
     @VisibleForTesting
     protected static class NativeWrapper {
+        public int openUinputDpad(String deviceName, int vendorId, int productId, String phys) {
+            return nativeOpenUinputDpad(deviceName, vendorId, productId, phys);
+        }
+
         public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
             return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
         }
@@ -403,6 +441,10 @@
             return nativeCloseUinput(fd);
         }
 
+        public boolean writeDpadKeyEvent(int fd, int androidKeyCode, int action) {
+            return nativeWriteDpadKeyEvent(fd, androidKeyCode, action);
+        }
+
         public boolean writeKeyEvent(int fd, int androidKeyCode, int action) {
             return nativeWriteKeyEvent(fd, androidKeyCode, action);
         }
@@ -433,10 +475,12 @@
         static final int TYPE_KEYBOARD = 1;
         static final int TYPE_MOUSE = 2;
         static final int TYPE_TOUCHSCREEN = 3;
+        static final int TYPE_DPAD = 4;
         @IntDef(prefix = { "TYPE_" }, value = {
                 TYPE_KEYBOARD,
                 TYPE_MOUSE,
                 TYPE_TOUCHSCREEN,
+                TYPE_DPAD,
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface Type {
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 cca3212..4204162 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -344,6 +344,33 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @Override // Binder call
+    public void createVirtualDpad(
+            int displayId,
+            @NonNull String deviceName,
+            int vendorId,
+            int productId,
+            @NonNull IBinder deviceToken) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to create a virtual dpad");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new SecurityException(
+                        "Cannot create a virtual dpad for a display not associated with "
+                                + "this virtual device");
+            }
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
+                    displayId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override // Binder call
     public void createVirtualKeyboard(
             int displayId,
@@ -437,6 +464,16 @@
     }
 
     @Override // Binder call
+    public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendDpadKeyEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override // Binder call
     public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
         final long binderToken = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 86c9770..a97173d 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -29,6 +29,7 @@
 import static com.android.server.am.ActivityManagerService.TAG_MU;
 import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PROVIDER;
 
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -48,7 +49,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
 import android.content.pm.UserInfo;
@@ -960,20 +960,22 @@
     String getProviderMimeType(Uri uri, int userId) {
         mService.enforceNotIsolatedCaller("getProviderMimeType");
         final String name = uri.getAuthority();
-        int callingUid = Binder.getCallingUid();
-        int callingPid = Binder.getCallingPid();
-        long ident = 0;
-        boolean clearedIdentity = false;
-        userId = mService.mUserController.unsafeConvertIncomingUser(userId);
-        if (canClearIdentity(callingPid, callingUid, userId)) {
-            clearedIdentity = true;
-            ident = Binder.clearCallingIdentity();
-        }
-        ContentProviderHolder holder = null;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
+        final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
+                ? Binder.clearCallingIdentity() : 0;
+        final ContentProviderHolder holder;
         try {
-            holder = getContentProviderExternalUnchecked(name, null, callingUid,
-                    "*getmimetype*", userId);
-            if (holder != null) {
+            holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
+                    "*getmimetype*", safeUserId);
+        } finally {
+            if (ident != 0) {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+        try {
+            if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
                 final IBinder providerConnection = holder.connection;
                 final ComponentName providerName = holder.info.getComponentName();
                 // Note: creating a new Runnable instead of using a lambda here since lambdas in
@@ -992,6 +994,13 @@
                     return holder.provider.getType(uri);
                 } finally {
                     mService.mHandler.removeCallbacks(providerNotResponding);
+                    // We need to clear the identity to call removeContentProviderExternalUnchecked
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        removeContentProviderExternalUnchecked(name, null /* token */, safeUserId);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -1000,18 +1009,6 @@
         } catch (Exception e) {
             Log.w(TAG, "Exception while determining type of " + uri, e);
             return null;
-        } finally {
-            // We need to clear the identity to call removeContentProviderExternalUnchecked
-            if (!clearedIdentity) {
-                ident = Binder.clearCallingIdentity();
-            }
-            try {
-                if (holder != null) {
-                    removeContentProviderExternalUnchecked(name, null, userId);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
         }
 
         return null;
@@ -1029,12 +1026,20 @@
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
         final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
-        final long ident = canClearIdentity(callingPid, callingUid, userId)
+        final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
                 ? Binder.clearCallingIdentity() : 0;
+        final ContentProviderHolder holder;
         try {
-            final ContentProviderHolder holder = getContentProviderExternalUnchecked(name, null,
-                    callingUid, "*getmimetype*", safeUserId);
-            if (holder != null) {
+            holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
+                    "*getmimetype*", safeUserId);
+        } finally {
+            if (ident != 0) {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        try {
+            if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
                 holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
                     final long identity = Binder.clearCallingIdentity();
                     try {
@@ -1050,8 +1055,6 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Content provider dead retrieving " + uri, e);
             resultCallback.sendResult(Bundle.EMPTY);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -1067,6 +1070,35 @@
                         callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean isHolderVisibleToCaller(@Nullable ContentProviderHolder holder, int callingUid,
+            @UserIdInt int userId) {
+        if (holder == null || holder.info == null) {
+            return false;
+        }
+
+        if (isAuthorityRedirectedForCloneProfile(holder.info.authority)
+                && resolveParentUserIdForCloneProfile(userId) != userId) {
+            // Since clone profile shares certain providers with its parent and the access is
+            // re-directed as well, the holder may not actually be installed on the clone profile.
+            return !mService.getPackageManagerInternal().filterAppAccess(holder.info.packageName,
+                    callingUid, userId, false /* filterUninstalled */);
+        }
+
+        return !mService.getPackageManagerInternal().filterAppAccess(holder.info.packageName,
+                callingUid, userId);
+    }
+
+    private static @UserIdInt int resolveParentUserIdForCloneProfile(@UserIdInt int userId) {
+        final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+        final UserInfo userInfo = umInternal.getUserInfo(userId);
+
+        if (userInfo == null || !userInfo.isCloneProfile()) {
+            return userId;
+        }
+
+        return umInternal.getProfileParentId(userId);
+    }
+
     /**
      * Check if the calling UID has a possible chance at accessing the provider
      * at the given authority and user.
@@ -1135,9 +1167,7 @@
                     "*checkContentProviderUriPermission*", userId);
             if (holder != null) {
 
-                final PackageManagerInternal packageManagerInt = LocalServices.getService(
-                        PackageManagerInternal.class);
-                final AndroidPackage androidPackage = packageManagerInt
+                final AndroidPackage androidPackage = mService.getPackageManagerInternal()
                         .getPackage(Binder.getCallingUid());
                 if (androidPackage == null) {
                     return PackageManager.PERMISSION_DENIED;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 4a7e631..af73c2b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4373,9 +4373,8 @@
                 final PackageManager pm = mContext.getPackageManager();
                 final String supplementalPackageName = pm.getSdkSandboxPackageName();
                 if (Objects.equals(packageName, supplementalPackageName)) {
-                    int supplementalAppId = pm.getPackageUid(supplementalPackageName,
-                            PackageManager.PackageInfoFlags.of(0));
-                    uid = UserHandle.getUid(UserHandle.getUserId(uid), supplementalAppId);
+                    uid = pm.getPackageUidAsUser(supplementalPackageName,
+                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Shouldn't happen for the supplemental package
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 99bbc3f..32ff5e22 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -71,6 +71,9 @@
     private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
 
     private static final boolean WAKE_ON_HOTPLUG = false;
+    private static final int MAX_CHANNELS = 8;
+    private static final HashMap<Integer, List<Integer>> AUDIO_CODECS_MAP =
+            mapAudioCodecWithAudioFormat();
 
     // Whether the System Audio Control feature is enabled or not. True by default.
     @GuardedBy("mLock")
@@ -485,17 +488,17 @@
             }
         }
 
-        @AudioCodec int[] audioFormatCodes = parseAudioFormatCodes(message.getParams());
+        @AudioCodec int[] audioCodecs = parseAudioCodecs(message.getParams());
         byte[] sadBytes;
         if (config != null && config.size() > 0) {
-            sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioFormatCodes);
+            sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioCodecs);
         } else {
             AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
             if (deviceInfo == null) {
                 return Constants.ABORT_UNABLE_TO_DETERMINE;
             }
 
-            sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes);
+            sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioCodecs);
         }
 
         if (sadBytes.length == 0) {
@@ -508,11 +511,12 @@
         }
     }
 
-    private byte[] getSupportedShortAudioDescriptors(
-            AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
-        ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
-        for (@AudioCodec int audioFormatCode : audioFormatCodes) {
-            byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
+    @VisibleForTesting
+    byte[] getSupportedShortAudioDescriptors(
+            AudioDeviceInfo deviceInfo, @AudioCodec int[] audioCodecs) {
+        ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length);
+        for (@AudioCodec int audioCodec : audioCodecs) {
+            byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioCodec);
             if (sad != null) {
                 if (sad.length == 3) {
 
@@ -520,7 +524,7 @@
                 } else {
                     HdmiLogger.warning(
                             "Dropping Short Audio Descriptor with length %d for requested codec %x",
-                            sad.length, audioFormatCode);
+                            sad.length, audioCodec);
                 }
             }
         }
@@ -528,7 +532,7 @@
     }
 
     private byte[] getSupportedShortAudioDescriptorsFromConfig(
-            List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) {
+            List<DeviceConfig> deviceConfig, @AudioCodec int[] audioCodecs) {
         DeviceConfig deviceConfigToUse = null;
         String audioDeviceName = SystemProperties.get(
                 Constants.PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT,
@@ -544,13 +548,13 @@
             return new byte[0];
         }
         HashMap<Integer, byte[]> map = new HashMap<>();
-        ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
+        ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length);
         for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
             map.put(codecSad.audioCodec, codecSad.sad);
         }
-        for (int i = 0; i < audioFormatCodes.length; i++) {
-            if (map.containsKey(audioFormatCodes[i])) {
-                byte[] sad = map.get(audioFormatCodes[i]);
+        for (int i = 0; i < audioCodecs.length; i++) {
+            if (map.containsKey(audioCodecs[i])) {
+                byte[] sad = map.get(audioCodecs[i]);
                 if (sad != null && sad.length == 3) {
                     sads.add(sad);
                 }
@@ -572,42 +576,171 @@
 
     /**
      * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
-     * audioFormatCode is not supported.
+     * audioCodec is not supported.
      */
     @Nullable
-    private byte[] getSupportedShortAudioDescriptor(
-            AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
-        switch (audioFormatCode) {
-            case Constants.AUDIO_CODEC_NONE: {
-                return null;
-            }
-            case Constants.AUDIO_CODEC_LPCM: {
-                return getLpcmShortAudioDescriptor(deviceInfo);
-            }
-            // TODO(b/80297701): implement the rest of the codecs
-            case Constants.AUDIO_CODEC_DD:
-            case Constants.AUDIO_CODEC_MPEG1:
-            case Constants.AUDIO_CODEC_MP3:
-            case Constants.AUDIO_CODEC_MPEG2:
-            case Constants.AUDIO_CODEC_AAC:
-            case Constants.AUDIO_CODEC_DTS:
-            case Constants.AUDIO_CODEC_ATRAC:
-            case Constants.AUDIO_CODEC_ONEBITAUDIO:
-            case Constants.AUDIO_CODEC_DDP:
-            case Constants.AUDIO_CODEC_DTSHD:
-            case Constants.AUDIO_CODEC_TRUEHD:
-            case Constants.AUDIO_CODEC_DST:
-            case Constants.AUDIO_CODEC_WMAPRO:
-            default: {
-                return null;
+    @VisibleForTesting
+    byte[] getSupportedShortAudioDescriptor(
+            AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) {
+        byte[] shortAudioDescriptor = new byte[3];
+
+        int[] deviceSupportedAudioFormats = deviceInfo.getEncodings();
+        // Return null when audioCodec or device does not support any audio formats.
+        if (!AUDIO_CODECS_MAP.containsKey(audioCodec) || deviceSupportedAudioFormats.length == 0) {
+            return null;
+        }
+        List<Integer> audioCodecSupportedAudioFormats = AUDIO_CODECS_MAP.get(audioCodec);
+
+        for (int supportedAudioFormat : deviceSupportedAudioFormats) {
+            if (audioCodecSupportedAudioFormats.contains(supportedAudioFormat)) {
+                // Initialise the first two bytes of short audio descriptor.
+                shortAudioDescriptor[0] = getFirstByteOfSAD(deviceInfo, audioCodec);
+                shortAudioDescriptor[1] = getSecondByteOfSAD(deviceInfo);
+                switch (audioCodec) {
+                    case Constants.AUDIO_CODEC_NONE: {
+                        return null;
+                    }
+                    case Constants.AUDIO_CODEC_LPCM: {
+                        if (supportedAudioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+                            shortAudioDescriptor[2] = (byte) 0x01;
+                        } else if (supportedAudioFormat
+                                == AudioFormat.ENCODING_PCM_24BIT_PACKED) {
+                            shortAudioDescriptor[2] = (byte) 0x04;
+                        } else {
+                            // Since no bit is reserved for these audio formats in LPCM codec.
+                            shortAudioDescriptor[2] = (byte) 0x00;
+                        }
+                        return shortAudioDescriptor;
+                    }
+                    case Constants.AUDIO_CODEC_DD:
+                    case Constants.AUDIO_CODEC_MPEG1:
+                    case Constants.AUDIO_CODEC_MP3:
+                    case Constants.AUDIO_CODEC_MPEG2:
+                    case Constants.AUDIO_CODEC_AAC:
+                    case Constants.AUDIO_CODEC_DTS: {
+                        shortAudioDescriptor[2] = getThirdSadByteForCodecs2Through8(deviceInfo);
+                        return shortAudioDescriptor;
+                    }
+                    case Constants.AUDIO_CODEC_DDP:
+                    case Constants.AUDIO_CODEC_DTSHD:
+                    case Constants.AUDIO_CODEC_TRUEHD: {
+                        // Default value is 0x0 unless defined by Audio Codec Vendor.
+                        shortAudioDescriptor[2] = (byte) 0x00;
+                        return shortAudioDescriptor;
+                    }
+                    case Constants.AUDIO_CODEC_ATRAC:
+                    case Constants.AUDIO_CODEC_ONEBITAUDIO:
+                    case Constants.AUDIO_CODEC_DST:
+                    case Constants.AUDIO_CODEC_WMAPRO:
+                        // Not supported.
+                    default: {
+                        return null;
+                    }
+                }
             }
         }
+        return null;
     }
 
-    @Nullable
-    private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
-        // TODO(b/80297701): implement
-        return null;
+    private static HashMap<Integer, List<Integer>> mapAudioCodecWithAudioFormat() {
+        // Mapping the values of @AudioCodec audio codecs with @AudioFormat audio formats.
+        HashMap<Integer, List<Integer>> audioCodecsMap = new HashMap<Integer, List<Integer>>();
+
+        audioCodecsMap.put(Constants.AUDIO_CODEC_NONE, List.of(AudioFormat.ENCODING_DEFAULT));
+        audioCodecsMap.put(
+                Constants.AUDIO_CODEC_LPCM,
+                List.of(
+                        AudioFormat.ENCODING_PCM_8BIT,
+                        AudioFormat.ENCODING_PCM_16BIT,
+                        AudioFormat.ENCODING_PCM_FLOAT,
+                        AudioFormat.ENCODING_PCM_24BIT_PACKED,
+                        AudioFormat.ENCODING_PCM_32BIT));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_DD, List.of(AudioFormat.ENCODING_AC3));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG1, List.of(AudioFormat.ENCODING_AAC_HE_V1));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG2, List.of(AudioFormat.ENCODING_AAC_HE_V2));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_MP3, List.of(AudioFormat.ENCODING_MP3));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_AAC, List.of(AudioFormat.ENCODING_AAC_LC));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_DTS, List.of(AudioFormat.ENCODING_DTS));
+        audioCodecsMap.put(
+                Constants.AUDIO_CODEC_DDP,
+                List.of(AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_E_AC3_JOC));
+        audioCodecsMap.put(Constants.AUDIO_CODEC_DTSHD, List.of(AudioFormat.ENCODING_DTS_HD));
+        audioCodecsMap.put(
+                Constants.AUDIO_CODEC_TRUEHD,
+                List.of(AudioFormat.ENCODING_DOLBY_TRUEHD, AudioFormat.ENCODING_DOLBY_MAT));
+
+        return audioCodecsMap;
+    }
+
+    private byte getFirstByteOfSAD(AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) {
+        byte firstByte = 0;
+        int maxNumberOfChannels = getMaxNumberOfChannels(deviceInfo);
+
+        // Fill bits 0-2 of the first byte.
+        firstByte |= (maxNumberOfChannels - 1);
+
+        // Fill bits 3-6 of the first byte.
+        firstByte |= (audioCodec << 3);
+
+        return firstByte;
+    }
+
+    private byte getSecondByteOfSAD(AudioDeviceInfo deviceInfo) {
+        ArrayList<Integer> samplingRates =
+                new ArrayList<Integer>(Arrays.asList(32, 44, 48, 88, 96, 176, 192));
+
+        // samplingRatesdevicesupports is guaranteed to be not null
+        int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates();
+        if (samplingRatesDeviceSupports.length == 0) {
+            Slog.e(TAG, "Device supports arbitrary rates");
+            // Since device supports arbitrary rates, we will return 0x7f since bit 7 is reserved.
+            return (byte) 0x7f;
+        }
+        byte secondByte = 0;
+        for (int supportedSampleRate : samplingRatesDeviceSupports) {
+            if (samplingRates.contains(supportedSampleRate)) {
+                int index = samplingRates.indexOf(supportedSampleRate);
+                // Setting the bit of a sample rate which is being supported.
+                secondByte |= (1 << index);
+            }
+        }
+
+        return secondByte;
+    }
+
+    /**
+     * Empty array from deviceInfo.getChannelCounts() implies device supports arbitrary channel
+     * counts and hence we assume max channels are supported by the device.
+     */
+    private int getMaxNumberOfChannels(AudioDeviceInfo deviceInfo) {
+        int maxNumberOfChannels = MAX_CHANNELS;
+        int[] channelCounts = deviceInfo.getChannelCounts();
+        if (channelCounts.length != 0) {
+            maxNumberOfChannels = channelCounts[channelCounts.length - 1];
+            maxNumberOfChannels =
+                    (maxNumberOfChannels > MAX_CHANNELS ? MAX_CHANNELS : maxNumberOfChannels);
+        }
+        return maxNumberOfChannels;
+    }
+
+    private byte getThirdSadByteForCodecs2Through8(AudioDeviceInfo deviceInfo) {
+        /*
+         * Here, we are assuming that max bit rate is closely equals to the max sampling rate the
+         * device supports.
+         */
+        int maxSamplingRate = 0;
+        int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates();
+        if (samplingRatesDeviceSupports.length == 0) {
+            maxSamplingRate = 192;
+        } else {
+            for (int sampleRate : samplingRatesDeviceSupports) {
+                if (maxSamplingRate < sampleRate) {
+                    maxSamplingRate = sampleRate;
+                }
+            }
+        }
+
+        return (byte) (maxSamplingRate / 8);
     }
 
     @Nullable
@@ -634,14 +767,14 @@
     }
 
     @AudioCodec
-    private int[] parseAudioFormatCodes(byte[] params) {
-        @AudioCodec int[] audioFormatCodes = new int[params.length];
+    private int[] parseAudioCodecs(byte[] params) {
+        @AudioCodec int[] audioCodecs = new int[params.length];
         for (int i = 0; i < params.length; i++) {
             byte val = params[i];
-            audioFormatCodes[i] =
-                val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
+            audioCodecs[i] =
+                    val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
         }
-        return audioFormatCodes;
+        return audioCodecs;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index c57d7e7..86732a1 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -18,12 +18,17 @@
 
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.BatteryState;
 import android.hardware.input.IInputDeviceBatteryListener;
 import android.hardware.input.InputManager;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UEventObserver;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -31,6 +36,8 @@
 import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.input.BatteryController.UEventManager.UEventListener;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -41,7 +48,7 @@
  * A thread-safe component of {@link InputManagerService} responsible for managing the battery state
  * of input devices.
  */
-final class BatteryController {
+final class BatteryController implements InputManager.InputDeviceListener {
     private static final String TAG = BatteryController.class.getSimpleName();
 
     // To enable these logs, run:
@@ -51,15 +58,35 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final NativeInputManagerService mNative;
+    private final Handler mHandler;
+    private final UEventManager mUEventManager;
 
     // Maps a pid to the registered listener record for that process. There can only be one battery
     // listener per process.
     @GuardedBy("mLock")
     private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
 
-    BatteryController(Context context, NativeInputManagerService nativeService) {
+    // Maps a deviceId that is being monitored to the battery state for the device.
+    // This must be kept in sync with {@link #mListenerRecords}.
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, MonitoredDeviceState> mMonitoredDeviceStates = new ArrayMap<>();
+
+    BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
+        this(context, nativeService, looper, new UEventManager() {});
+    }
+
+    @VisibleForTesting
+    BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
+            UEventManager uEventManager) {
         mContext = context;
         mNative = nativeService;
+        mHandler = new Handler(looper);
+        mUEventManager = uEventManager;
+    }
+
+    void systemRunning() {
+        Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+                .registerInputDeviceListener(this, mHandler);
     }
 
     /**
@@ -96,29 +123,45 @@
                                 + " is already monitoring deviceId " + deviceId);
             }
 
+            MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
+            if (deviceState == null) {
+                // This is the first listener that is monitoring this device.
+                deviceState = new MonitoredDeviceState(deviceId);
+                mMonitoredDeviceStates.put(deviceId, deviceState);
+            }
+
             if (DEBUG) {
                 Slog.d(TAG, "Battery listener for pid " + pid
                         + " is monitoring deviceId " + deviceId);
             }
 
-            notifyBatteryListener(deviceId, listenerRecord);
+            notifyBatteryListener(listenerRecord, deviceState);
         }
     }
 
-    private void notifyBatteryListener(int deviceId, ListenerRecord record) {
-        final long eventTime = SystemClock.uptimeMillis();
+    private static void notifyBatteryListener(ListenerRecord listenerRecord,
+            MonitoredDeviceState deviceState) {
         try {
-            record.mListener.onBatteryStateChanged(
-                    deviceId,
-                    hasBattery(deviceId),
-                    mNative.getBatteryStatus(deviceId),
-                    mNative.getBatteryCapacity(deviceId) / 100.f,
-                    eventTime);
+            listenerRecord.mListener.onBatteryStateChanged(
+                    deviceState.mDeviceId,
+                    deviceState.mHasBattery,
+                    deviceState.mBatteryStatus,
+                    deviceState.mBatteryCapacity,
+                    deviceState.mLastUpdateTime);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to notify listener", e);
         }
     }
 
+    @GuardedBy("mLock")
+    private void notifyAllListenersForDeviceLocked(MonitoredDeviceState deviceState) {
+        mListenerRecords.forEach((pid, listenerRecord) -> {
+            if (listenerRecord.mMonitoredDevices.contains(deviceState.mDeviceId)) {
+                notifyBatteryListener(listenerRecord, deviceState);
+            }
+        });
+    }
+
     private boolean hasBattery(int deviceId) {
         final InputDevice device =
                 Objects.requireNonNull(mContext.getSystemService(InputManager.class))
@@ -126,6 +169,12 @@
         return device != null && device.hasBattery();
     }
 
+    @GuardedBy("mLock")
+    private MonitoredDeviceState getDeviceStateOrThrowLocked(int deviceId) {
+        return Objects.requireNonNull(mMonitoredDeviceStates.get(deviceId),
+                "Maps are out of sync: Cannot find device state for deviceId " + deviceId);
+    }
+
     /**
      * Unregister the battery listener for the given input device and stop monitoring its battery
      * state. If there are no other input devices that this listener is monitoring, the listener is
@@ -170,6 +219,13 @@
                     + pid);
         }
 
+        if (!hasRegisteredListenerForDeviceLocked(deviceId)) {
+            // There are no more listeners monitoring this device.
+            final MonitoredDeviceState deviceState = getDeviceStateOrThrowLocked(deviceId);
+            deviceState.stopMonitoring();
+            mMonitoredDeviceStates.remove(deviceId);
+        }
+
         if (listenerRecord.mMonitoredDevices.isEmpty()) {
             // There are no more devices being monitored by this listener.
             listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0);
@@ -178,6 +234,16 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private boolean hasRegisteredListenerForDeviceLocked(int deviceId) {
+        for (int i = 0; i < mListenerRecords.size(); i++) {
+            if (mListenerRecords.valueAt(i).mMonitoredDevices.contains(deviceId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void handleListeningProcessDied(int pid) {
         synchronized (mLock) {
             final ListenerRecord listenerRecord = mListenerRecords.get(pid);
@@ -194,6 +260,19 @@
         }
     }
 
+    // Query the battery state for the device and notify all listeners if there is a change.
+    private void handleBatteryChangeNotification(int deviceId, long eventTime) {
+        synchronized (mLock) {
+            final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
+            if (deviceState == null) {
+                return;
+            }
+            if (deviceState.updateBatteryState(eventTime)) {
+                notifyAllListenersForDeviceLocked(deviceState);
+            }
+        }
+    }
+
     void dump(PrintWriter pw, String prefix) {
         synchronized (mLock) {
             pw.println(prefix + TAG + ": " + mListenerRecords.size()
@@ -211,6 +290,29 @@
         }
     }
 
+    @VisibleForTesting
+    @Override
+    public void onInputDeviceAdded(int deviceId) {}
+
+    @VisibleForTesting
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {}
+
+    @VisibleForTesting
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        synchronized (mLock) {
+            final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
+            if (deviceState == null) {
+                return;
+            }
+            final long eventTime = SystemClock.uptimeMillis();
+            if (deviceState.updateBatteryState(eventTime)) {
+                notifyAllListenersForDeviceLocked(deviceState);
+            }
+        }
+    }
+
     // A record of a registered battery listener from one process.
     private class ListenerRecord {
         final int mPid;
@@ -232,4 +334,109 @@
                     + ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray());
         }
     }
+
+    // Holds the state of an InputDevice for which battery changes are currently being monitored.
+    private class MonitoredDeviceState {
+        private final int mDeviceId;
+
+        private long mLastUpdateTime = 0;
+        private boolean mHasBattery = false;
+        @BatteryState.BatteryStatus
+        private int mBatteryStatus = BatteryState.STATUS_UNKNOWN;
+        private float mBatteryCapacity = Float.NaN;
+
+        @Nullable
+        private UEventListener mUEventListener;
+
+        MonitoredDeviceState(int deviceId) {
+            mDeviceId = deviceId;
+
+            // Load the initial battery state and start monitoring.
+            final long eventTime = SystemClock.uptimeMillis();
+            updateBatteryState(eventTime);
+        }
+
+        // Returns true if the battery state changed since the last time it was updated.
+        boolean updateBatteryState(long eventTime) {
+            mLastUpdateTime = eventTime;
+
+            final boolean batteryPresenceChanged = mHasBattery != hasBattery(mDeviceId);
+            if (batteryPresenceChanged) {
+                mHasBattery = !mHasBattery;
+                if (mHasBattery) {
+                    startMonitoring();
+                } else {
+                    stopMonitoring();
+                }
+            }
+
+            final int oldStatus = mBatteryStatus;
+            final float oldCapacity = mBatteryCapacity;
+
+            if (mHasBattery) {
+                mBatteryStatus = mNative.getBatteryStatus(mDeviceId);
+                mBatteryCapacity = mNative.getBatteryCapacity(mDeviceId) / 100.f;
+            } else {
+                mBatteryStatus = BatteryState.STATUS_UNKNOWN;
+                mBatteryCapacity = Float.NaN;
+            }
+
+            return batteryPresenceChanged
+                    || mBatteryStatus != oldStatus
+                    || mBatteryCapacity != oldCapacity;
+        }
+
+        private void startMonitoring() {
+            final String batteryPath = mNative.getBatteryDevicePath(mDeviceId);
+            if (batteryPath == null) {
+                return;
+            }
+            mUEventListener = new UEventListener() {
+                @Override
+                void onUEvent(long eventTime) {
+                    handleBatteryChangeNotification(mDeviceId, eventTime);
+                }
+            };
+            mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath);
+        }
+
+        // This must be called when the device is no longer being monitored.
+        void stopMonitoring() {
+            if (mUEventListener != null) {
+                mUEventManager.removeListener(mUEventListener);
+                mUEventListener = null;
+            }
+        }
+    }
+
+    // An interface used to change the API of UEventObserver to a more test-friendly format.
+    @VisibleForTesting
+    interface UEventManager {
+
+        @VisibleForTesting
+        abstract class UEventListener {
+            private final UEventObserver mObserver = new UEventObserver() {
+                @Override
+                public void onUEvent(UEvent event) {
+                    final long eventTime = SystemClock.uptimeMillis();
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "UEventListener: Received UEvent: "
+                                        + event + " eventTime: " + eventTime);
+                    }
+                    UEventListener.this.onUEvent(eventTime);
+                }
+            };
+
+            abstract void onUEvent(long eventTime);
+        }
+
+        default void addListener(UEventListener listener, String match) {
+            listener.mObserver.startObserving(match);
+        }
+
+        default void removeListener(UEventListener listener) {
+            listener.mObserver.stopObserving();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 3def714..4da0c0d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -421,7 +421,7 @@
         mContext = injector.getContext();
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
-        mBatteryController = new BatteryController(mContext, mNative);
+        mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
                 mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -561,6 +561,8 @@
         if (mWiredAccessoryCallbacks != null) {
             mWiredAccessoryCallbacks.systemReady();
         }
+
+        mBatteryController.systemRunning();
     }
 
     private void reloadKeyboardLayouts() {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0fac808..4d55d4e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1786,8 +1786,8 @@
          * from receiving events from the profile.
          */
         public boolean isPermittedForProfile(int userId) {
-            if (!mUserProfiles.canProfileUseBoundServices(userId)) {
-                return false;
+            if (!mUserProfiles.isProfileUser(userId)) {
+                return true;
             }
             DevicePolicyManager dpm =
                     (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE);
@@ -1862,16 +1862,16 @@
             }
         }
 
-        public boolean canProfileUseBoundServices(int userId) {
+        public boolean isProfileUser(int userId) {
             synchronized (mCurrentProfiles) {
                 UserInfo user = mCurrentProfiles.get(userId);
                 if (user == null) {
                     return false;
                 }
                 if (user.isManagedProfile() || user.isCloneProfile()) {
-                    return false;
+                    return true;
                 }
-                return true;
+                return false;
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5d64176..4b18add 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1829,7 +1829,7 @@
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 mUserProfiles.updateCache(context);
-                if (mUserProfiles.canProfileUseBoundServices(userId)) {
+                if (!mUserProfiles.isProfileUser(userId)) {
                     // reload per-user settings
                     mSettingsObserver.update(null);
                     // Refresh managed services
@@ -1843,7 +1843,7 @@
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 if (userId != USER_NULL) {
                     mUserProfiles.updateCache(context);
-                    if (mUserProfiles.canProfileUseBoundServices(userId)) {
+                    if (!mUserProfiles.isProfileUser(userId)) {
                         allowDefaultApprovedServices(userId);
                     }
                 }
@@ -1861,7 +1861,7 @@
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 mUserProfiles.updateCache(context);
                 mAssistants.onUserUnlocked(userId);
-                if (mUserProfiles.canProfileUseBoundServices(userId)) {
+                if (!mUserProfiles.isProfileUser(userId)) {
                     mConditionProviders.onUserUnlocked(userId);
                     mListeners.onUserUnlocked(userId);
                     mZenModeHelper.onUserUnlocked(userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 1c78528..baa471c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -23,7 +23,9 @@
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.Intent.EXTRA_REASON;
+import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
@@ -39,6 +41,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -56,6 +59,7 @@
 import android.content.res.ApkAssets;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.FabricatedOverlayInternal;
 import android.os.HandlerThread;
@@ -1009,7 +1013,9 @@
                 }
                 opti++;
 
-                if ("-h".equals(opt)) {
+                if ("-a".equals(opt)) {
+                    // dumpsys will pass in -a; silently ignore it
+                } else if ("-h".equals(opt)) {
                     pw.println("dump [-h] [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
                     pw.println("  Print debugging information about the overlay manager.");
                     pw.println("  With optional parameter PACKAGE, limit output to the specified");
@@ -1417,20 +1423,41 @@
 
     private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
             final int userId) {
+        final ActivityManagerInternal amInternal =
+                LocalServices.getService(ActivityManagerInternal.class);
         CollectionUtils.forEach(targetPackages, target -> {
             final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
                     Uri.fromParts("package", target, null));
             intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            try {
-                ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null,
-                        null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e);
-            }
+            intent.putExtra(EXTRA_PACKAGE_NAME, target);
+            intent.putExtra(EXTRA_USER_ID, userId);
+            amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */,
+                    false /* serialized */, userId, null /* appIdAllowList */,
+                    OverlayManagerService::filterReceiverAccess, null /* bOptions */);
         });
     }
 
     /**
+     * A callback from the broadcast queue to determine whether the intent
+     * {@link Intent#ACTION_OVERLAY_CHANGED} is visible to the receiver.
+     *
+     * @param callingUid The receiver's uid.
+     * @param extras The extras of intent that contains {@link Intent#EXTRA_PACKAGE_NAME} and
+     * {@link Intent#EXTRA_USER_ID} to check.
+     * @return {@code null} if the intent is not visible to the receiver.
+     */
+    @Nullable
+    private static Bundle filterReceiverAccess(int callingUid, @NonNull Bundle extras) {
+        final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+        final int userId = extras.getInt(EXTRA_USER_ID);
+        if (LocalServices.getService(PackageManagerInternal.class).filterAppAccess(
+                packageName, callingUid, userId, false /* filterUninstalled */)) {
+            return null;
+        }
+        return extras;
+    }
+
+    /**
      * Tell the activity manager to tell a set of packages to reload their
      * resources.
      */
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 9f6aa63..7242a56 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -271,7 +271,8 @@
         // other processes clean up before deleting resources.
         synchronized (mPm.mInstallLock) {
             if (info.mArgs != null) {
-                mRemovePackageHelper.cleanUpResources(info.mArgs);
+                mRemovePackageHelper.cleanUpResources(info.mArgs.mCodeFile,
+                        info.mArgs.mInstructionSets);
             }
 
             boolean reEnableStub = false;
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index 65c2378..a94a4e2 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -70,10 +70,9 @@
             UserHandle user, String[] instructionSets,
             String abiOverride, String[] installGrantPermissions,
             List<String> allowlistedRestrictedPermissions,
-            int autoRevokePermissionsMode,
-            String traceMethod, int traceCookie, SigningDetails signingDetails,
-            int installReason, int installScenario, boolean forceQueryableOverride,
-            int dataLoaderType, int packageSource) {
+            int autoRevokePermissionsMode, String traceMethod, int traceCookie,
+            SigningDetails signingDetails, int installReason, int installScenario,
+            boolean forceQueryableOverride, int dataLoaderType, int packageSource) {
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mInstallFlags = installFlags;
@@ -96,18 +95,6 @@
         mPackageSource = packageSource;
     }
 
-    /** New install */
-    InstallArgs(InstallingSession params) {
-        this(params.mOriginInfo, params.mMoveInfo, params.mObserver, params.mInstallFlags,
-                params.mInstallSource, params.mVolumeUuid,
-                params.getUser(), null /*instructionSets*/, params.mPackageAbiOverride,
-                params.mGrantedRuntimePermissions, params.mAllowlistedRestrictedPermissions,
-                params.mAutoRevokePermissionsMode,
-                params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
-                params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
-                params.mDataLoaderType, params.mPackageSource);
-    }
-
     /**
      * Create args that describe an existing installed package. Typically used
      * when cleaning up old installs, or used as a move source.
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d201710..a25ee5e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -105,7 +105,6 @@
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderType;
-import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
@@ -147,6 +146,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
@@ -275,9 +275,9 @@
                 // Prune unused SharedUserSetting
                 if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) {
                     // Set the app ID in removed info for UID_REMOVED broadcasts
-                    if (reconciledPkg.mInstallResult != null
-                            && reconciledPkg.mInstallResult.mRemovedInfo != null) {
-                        reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId =
+                    if (reconciledPkg.mInstallRequest != null
+                            && reconciledPkg.mInstallRequest.getRemovedInfo() != null) {
+                        reconciledPkg.mInstallRequest.getRemovedInfo().mRemovedAppId =
                                 requestSharedUserSetting.mAppId;
                     }
                 }
@@ -307,24 +307,26 @@
                 mPm.mSettings.convertSharedUserSettingsLPw(sharedUserSetting);
             }
         }
-        if (reconciledPkg.mInstallArgs != null
-                && reconciledPkg.mInstallArgs.mForceQueryableOverride) {
+        if (reconciledPkg.mInstallRequest != null
+                && reconciledPkg.mInstallRequest.isForceQueryableOverride()) {
             pkgSetting.setForceQueryableOverride(true);
         }
 
         // If this is part of a standard install, set the initiating package name, else rely on
         // previous device state.
-        if (reconciledPkg.mInstallArgs != null) {
-            InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource;
-            if (installSource.initiatingPackageName != null) {
-                final PackageSetting ips = mPm.mSettings.getPackageLPr(
-                        installSource.initiatingPackageName);
-                if (ips != null) {
-                    installSource = installSource.setInitiatingPackageSignatures(
-                            ips.getSignatures());
+        if (reconciledPkg.mInstallRequest != null) {
+            InstallSource installSource = reconciledPkg.mInstallRequest.getInstallSource();
+            if (installSource != null) {
+                if (installSource.initiatingPackageName != null) {
+                    final PackageSetting ips = mPm.mSettings.getPackageLPr(
+                            installSource.initiatingPackageName);
+                    if (ips != null) {
+                        installSource = installSource.setInitiatingPackageSignatures(
+                                ips.getSignatures());
+                    }
                 }
+                pkgSetting.setInstallSource(installSource);
             }
-            pkgSetting.setInstallSource(installSource);
         }
 
         if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
@@ -418,8 +420,8 @@
                         reconciledPkg.mAllowedSharedLibraryInfos,
                         reconciledPkg.getCombinedAvailablePackages(), scanFlags);
 
-        if (reconciledPkg.mInstallResult != null) {
-            reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs;
+        if (reconciledPkg.mInstallRequest != null) {
+            reconciledPkg.mInstallRequest.setLibraryConsumers(clientLibPkgs);
         }
 
         if ((scanFlags & SCAN_BOOTING) != 0) {
@@ -615,20 +617,18 @@
                     mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId });
                 }
                 // start async restore with no post-install since we finish install here
-                PackageInstalledInfo res = new PackageInstalledInfo(
-                        PackageManager.INSTALL_SUCCEEDED);
-                res.mPkg = pkgSetting.getPkg();
-                res.mNewUsers = new int[]{ userId };
 
-                PostInstallData postInstallData =
-                        new PostInstallData(null, res, () -> {
+                InstallRequest request = new InstallRequest(userId,
+                        PackageManager.INSTALL_SUCCEEDED, pkgSetting.getPkg(), new int[]{ userId },
+                        () -> {
                             mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
                                     userId);
                             if (intentSender != null) {
-                                onRestoreComplete(res.mReturnCode, mContext, intentSender);
+                                onRestoreComplete(PackageManager.INSTALL_SUCCEEDED, mContext,
+                                        intentSender);
                             }
                         });
-                restoreAndPostInstall(userId, res, postInstallData);
+                restoreAndPostInstall(request);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -647,18 +647,17 @@
         }
     }
 
-    /** @param data Post-install is performed only if this is non-null. */
-    public void restoreAndPostInstall(
-            int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
+    public void restoreAndPostInstall(InstallRequest request) {
+        final int userId = request.getUserId();
         if (DEBUG_INSTALL) {
-            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg);
+            Log.v(TAG,
+                    "restoreAndPostInstall userId=" + userId + " package=" + request.getPkg());
         }
 
         // A restore should be requested at this point if (a) the install
         // succeeded, (b) the operation is not an update.
-        final boolean update = res.mRemovedInfo != null
-                && res.mRemovedInfo.mRemovedPackage != null;
-        boolean doRestore = !update && res.mPkg != null;
+        final boolean update = request.isUpdate();
+        boolean doRestore = !update && request.getPkg() != null;
 
         // Set up the post-install work request bookkeeping.  This will be used
         // and cleaned up by the post-install event handling regardless of whether
@@ -666,23 +665,17 @@
         int token;
         if (mPm.mNextInstallToken < 0) mPm.mNextInstallToken = 1;
         token = mPm.mNextInstallToken++;
-        if (data != null) {
-            mPm.mRunningInstalls.put(token, data);
-        } else if (DEBUG_INSTALL) {
-            Log.v(TAG, "No post-install required for " + token);
-        }
+        mPm.mRunningInstalls.put(token, request);
 
         if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
 
-        if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+        if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED && doRestore) {
             // Pass responsibility to the Backup Manager.  It will perform a
             // restore if appropriate, then pass responsibility back to the
             // Package Manager to run the post-install observer callbacks
             // and broadcasts.
-            if (res.mFreezer != null) {
-                res.mFreezer.close();
-            }
-            doRestore = performBackupManagerRestore(userId, token, res);
+            request.closeFreezer();
+            doRestore = performBackupManagerRestore(userId, token, request);
         }
 
         // If this is an update to a package that might be potentially downgraded, then we
@@ -690,8 +683,8 @@
         // need to be snapshotted or restored for the package.
         //
         // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
-        if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
-            doRestore = performRollbackManagerRestore(userId, token, res, data);
+        if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+            doRestore = performRollbackManagerRestore(userId, token, request);
         }
 
         if (!doRestore) {
@@ -707,10 +700,10 @@
     }
 
     /**
-     * Perform Backup Manager restore for a given {@link PackageInstalledInfo}.
+     * Perform Backup Manager restore for a given {@link InstallRequest}.
      * Returns whether the restore successfully completed.
      */
-    private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
+    private boolean performBackupManagerRestore(int userId, int token, InstallRequest request) {
         IBackupManager iBackupManager = mInjector.getIBackupManager();
         if (iBackupManager != null) {
             // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
@@ -725,7 +718,7 @@
             try {
                 if (iBackupManager.isUserReadyForBackup(userId)) {
                     iBackupManager.restoreAtInstallForUser(
-                            userId, res.mPkg.getPackageName(), token);
+                            userId, request.getPkg().getPackageName(), token);
                 } else {
                     Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
                             + "didn't take place.");
@@ -745,12 +738,11 @@
     }
 
     /**
-     * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}.
+     * Perform Rollback Manager restore for a given {@link InstallRequest}.
      * Returns whether the restore successfully completed.
      */
-    private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
-            PostInstallData data) {
-        final String packageName = res.mPkg.getPackageName();
+    private boolean performRollbackManagerRestore(int userId, int token, InstallRequest request) {
+        final String packageName = request.getPkg().getPackageName();
         final int[] allUsers = mPm.mUserManager.getUserIds();
         final int[] installedUsers;
 
@@ -762,19 +754,20 @@
             if (ps != null) {
                 appId = ps.getAppId();
                 ceDataInode = ps.getCeDataInode(userId);
+                // NOTE: We ignore the user specified in the InstallParam because we know this is
+                // an update, and hence need to restore data for all installed users.
+                installedUsers = ps.queryInstalledUsers(allUsers, true);
+            } else {
+                installedUsers = new int[0];
             }
-
-            // NOTE: We ignore the user specified in the InstallParam because we know this is
-            // an update, and hence need to restore data for all installed users.
-            installedUsers = ps.queryInstalledUsers(allUsers, true);
         }
 
-        boolean doSnapshotOrRestore = data != null && data.args != null
-                && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
-                || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
+        final int installFlags = request.getInstallFlags();
+        boolean doSnapshotOrRestore = ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                || (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
 
         if (ps != null && doSnapshotOrRestore) {
-            final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
+            final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps);
             final RollbackManagerInternal rollbackManager =
                     mInjector.getLocalService(RollbackManagerInternal.class);
             rollbackManager.snapshotAndRestoreUserData(packageName,
@@ -817,9 +810,8 @@
     @GuardedBy("mPm.mInstallLock")
     private void installPackagesLI(List<InstallRequest> requests) {
         final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
-        final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());
-        final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());
         final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
+        final Map<String, InstallRequest> installRequests = new ArrayMap<>(requests.size());
         final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
         final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
         boolean success = false;
@@ -832,32 +824,30 @@
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
                     prepareResult =
-                            preparePackageLI(request.mArgs, request.mInstallResult);
+                            preparePackageLI(request);
                 } catch (PrepareFailure prepareFailure) {
-                    request.mInstallResult.setError(prepareFailure.error,
+                    request.setError(prepareFailure.error,
                             prepareFailure.getMessage());
-                    request.mInstallResult.mOrigPackage = prepareFailure.mConflictingPackage;
-                    request.mInstallResult.mOrigPermission = prepareFailure.mConflictingPermission;
+                    request.setOriginPackage(prepareFailure.mConflictingPackage);
+                    request.setOriginPermission(prepareFailure.mConflictingPermission);
                     return;
                 } finally {
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
-                request.mInstallResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
-                request.mInstallResult.mInstallerPackageName =
-                        request.mArgs.mInstallSource.installerPackageName;
+                request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+                request.setInstallerPackageName(request.getSourceInstallerPackageName());
 
                 final String packageName = prepareResult.mPackageToScan.getPackageName();
                 prepareResults.put(packageName, prepareResult);
-                installResults.put(packageName, request.mInstallResult);
-                installArgs.put(packageName, request.mArgs);
+                installRequests.put(packageName, request);
                 try {
                     final ScanResult result = scanPackageTracedLI(
                             prepareResult.mPackageToScan, prepareResult.mParseFlags,
                             prepareResult.mScanFlags, System.currentTimeMillis(),
-                            request.mArgs.mUser, request.mArgs.mAbiOverride);
+                            request.getUser(), request.getAbiOverride());
                     if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(),
                             result)) {
-                        request.mInstallResult.setError(
+                        request.setError(
                                 PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
                                 "Duplicate package "
                                         + result.mPkgSetting.getPkg().getPackageName()
@@ -868,7 +858,7 @@
                             result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) {
                         // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
                         //  signatures. Is there a better error code?
-                        request.mInstallResult.setError(
+                        request.setError(
                                 INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                 "Update attempted to change value of "
                                         + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
@@ -883,15 +873,15 @@
                     versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
                             mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
                 } catch (PackageManagerException e) {
-                    request.mInstallResult.setError("Scanning Failed.", e);
+                    request.setError("Scanning Failed.", e);
                     return;
                 }
             }
 
             CommitRequest commitRequest;
             synchronized (mPm.mLock) {
-                ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
-                        installResults, prepareResults,
+                ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans,
+                        installRequests, prepareResults,
                         Collections.unmodifiableMap(mPm.mPackages), versionInfos);
                 Map<String, ReconciledPackage> reconciledPackages;
                 try {
@@ -901,7 +891,7 @@
                             mPm.mSettings.getKeySetManagerService(), mPm.mSettings);
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
-                        request.mInstallResult.setError("Reconciliation failed...", e);
+                        request.setError("Reconciliation failed...", e);
                     }
                     return;
                 } finally {
@@ -921,25 +911,24 @@
         } finally {
             if (success) {
                 for (InstallRequest request : requests) {
-                    final InstallArgs args = request.mArgs;
-                    if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) {
+                    if (request.getDataLoaderType() != DataLoaderType.INCREMENTAL) {
                         continue;
                     }
-                    if (args.mSigningDetails.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
+                    if (request.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
                         continue;
                     }
                     // For incremental installs, we bypass the verifier prior to install. Now
                     // that we know the package is valid, send a notice to the verifier with
                     // the root hash of the base.apk.
-                    final String baseCodePath = request.mInstallResult.mPkg.getBaseApkPath();
-                    final String[] splitCodePaths = request.mInstallResult.mPkg.getSplitCodePaths();
-                    final Uri originUri = Uri.fromFile(args.mOriginInfo.mResolvedFile);
+                    final String baseCodePath = request.getPkg().getBaseApkPath();
+                    final String[] splitCodePaths = request.getPkg().getSplitCodePaths();
+                    final Uri originUri = request.getOriginUri();
                     final int verificationId = mPm.mPendingVerificationToken++;
                     final String rootHashString = PackageManagerServiceUtils
                             .buildVerificationRootHashString(baseCodePath, splitCodePaths);
                     VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                             PackageManager.VERIFICATION_ALLOW, rootHashString,
-                            args.mDataLoaderType, args.mUser, mContext);
+                            request.getDataLoaderType(), request.getUser(), mContext);
                 }
             } else {
                 for (ScanResult result : preparedScans.values()) {
@@ -951,11 +940,9 @@
                 // TODO(b/194319951): create a more descriptive reason than unknown
                 // mark all non-failure installs as UNKNOWN so we do not treat them as success
                 for (InstallRequest request : requests) {
-                    if (request.mInstallResult.mFreezer != null) {
-                        request.mInstallResult.mFreezer.close();
-                    }
-                    if (request.mInstallResult.mReturnCode == PackageManager.INSTALL_SUCCEEDED) {
-                        request.mInstallResult.mReturnCode = PackageManager.INSTALL_UNKNOWN;
+                    request.closeFreezer();
+                    if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+                        request.setReturnCode(PackageManager.INSTALL_UNKNOWN);
                     }
                 }
             }
@@ -980,18 +967,19 @@
     }
 
     @GuardedBy("mPm.mInstallLock")
-    private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
+    private PrepareResult preparePackageLI(InstallRequest request)
             throws PrepareFailure {
-        final int installFlags = args.mInstallFlags;
-        final boolean onExternal = args.mVolumeUuid != null;
+        final int installFlags = request.getInstallFlags();
+        final boolean onExternal = request.getVolumeUuid() != null;
         final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
         final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
         final boolean virtualPreload =
                 ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
         final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
-        final boolean isRollback = args.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+        final boolean isRollback =
+                request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
         @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
-        if (args.mMoveInfo != null) {
+        if (request.isMoveInstall()) {
             // moving a complete application; perform an initial scan on the new install location
             scanFlags |= SCAN_INITIAL;
         }
@@ -1012,7 +1000,7 @@
         }
 
         final File tmpPackageFile = new File(
-                isApex ? res.mApexInfo.modulePath : args.getCodePath());
+                isApex ? request.getApexInfo().modulePath : request.getCodePath());
         if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
 
         // Validity check
@@ -1066,7 +1054,8 @@
             }
         }
 
-        String pkgName = res.mName = parsedPackage.getPackageName();
+        String pkgName = parsedPackage.getPackageName();
+        request.setName(pkgName);
         if (parsedPackage.isTestOnly()) {
             if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
                 throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
@@ -1074,8 +1063,8 @@
         }
 
         // either use what we've been given or parse directly from the APK
-        if (args.mSigningDetails != SigningDetails.UNKNOWN) {
-            parsedPackage.setSigningDetails(args.mSigningDetails);
+        if (request.getSigningDetails() != SigningDetails.UNKNOWN) {
+            parsedPackage.setSigningDetails(request.getSigningDetails());
         } else {
             final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
             final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
@@ -1223,7 +1212,8 @@
                 if (ps.getPkg() != null) {
                     systemApp = ps.getPkg().isSystem();
                 }
-                res.mOrigUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true);
+                request.setOriginUsers(
+                        ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true));
             }
 
             final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups());
@@ -1299,7 +1289,7 @@
                         // it as dangerous leading to the group auto-grant.
                         if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE)
                                 == PermissionInfo.PROTECTION_DANGEROUS) {
-                            if (bp != null && !bp.isRuntime()) {
+                            if (!bp.isRuntime()) {
                                 Slog.w(TAG, "Package " + parsedPackage.getPackageName()
                                         + " trying to change a non-runtime permission "
                                         + perm.getName()
@@ -1372,7 +1362,7 @@
             }
         }
 
-        if (args.mMoveInfo != null) {
+        if (request.isMoveInstall()) {
             // We did an in-place move, so dex is ready to roll
             scanFlags |= SCAN_NO_DEX;
             scanFlags |= SCAN_MOVE;
@@ -1380,7 +1370,7 @@
             synchronized (mPm.mLock) {
                 final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
                 if (ps == null) {
-                    res.setError(INSTALL_FAILED_INTERNAL_ERROR,
+                    request.setError(INSTALL_FAILED_INTERNAL_ERROR,
                             "Missing settings for moved package " + pkgName);
                 }
 
@@ -1403,7 +1393,7 @@
                 }
                 boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
                         && pkgSetting.getPkgState().isUpdatedSystemApp();
-                final String abiOverride = deriveAbiOverride(args.mAbiOverride);
+                final String abiOverride = deriveAbiOverride(request.getAbiOverride());
                 boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
                         derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
@@ -1419,7 +1409,7 @@
         }
 
         if (!isApex) {
-            doRenameLI(args, res.mReturnCode, res.mReturnMsg, parsedPackage);
+            doRenameLI(request, parsedPackage);
 
             try {
                 setUpFsVerityIfPossible(parsedPackage);
@@ -1430,8 +1420,8 @@
             }
         } else {
             // Use the path returned by apexd
-            parsedPackage.setPath(res.mApexInfo.modulePath);
-            parsedPackage.setBaseApkPath(res.mApexInfo.modulePath);
+            parsedPackage.setPath(request.getApexInfo().modulePath);
+            parsedPackage.setBaseApkPath(request.getApexInfo().modulePath);
         }
 
         final PackageFreezer freezer =
@@ -1559,8 +1549,8 @@
 
                     // don't allow an upgrade from full to ephemeral
                     if (isInstantApp) {
-                        if (args.mUser == null
-                                || args.mUser.getIdentifier() == UserHandle.USER_ALL) {
+                        if (request.getUser() == null
+                                || request.getUserId() == UserHandle.USER_ALL) {
                             for (int currentUser : allUsers) {
                                 if (!ps.getInstantApp(currentUser)) {
                                     // can't downgrade from full to instant
@@ -1571,10 +1561,10 @@
                                             PackageManager.INSTALL_FAILED_SESSION_INVALID);
                                 }
                             }
-                        } else if (!ps.getInstantApp(args.mUser.getIdentifier())) {
+                        } else if (!ps.getInstantApp(request.getUserId())) {
                             // can't downgrade from full to instant
                             Slog.w(TAG, "Can't replace full app with instant app: " + pkgName11
-                                    + " for user: " + args.mUser.getIdentifier());
+                                    + " for user: " + request.getUserId());
                             throw new PrepareFailure(
                                     PackageManager.INSTALL_FAILED_SESSION_INVALID);
                         }
@@ -1582,25 +1572,29 @@
                 }
 
                 // Update what is removed
-                res.mRemovedInfo = new PackageRemovedInfo(mPm);
-                res.mRemovedInfo.mUid = oldPackage.getUid();
-                res.mRemovedInfo.mRemovedPackage = oldPackage.getPackageName();
-                res.mRemovedInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
-                res.mRemovedInfo.mIsStaticSharedLib =
+                PackageRemovedInfo removedInfo = new PackageRemovedInfo(mPm);
+                removedInfo.mUid = oldPackage.getUid();
+                removedInfo.mRemovedPackage = oldPackage.getPackageName();
+                removedInfo.mInstallerPackageName =
+                        ps.getInstallSource().installerPackageName;
+                removedInfo.mIsStaticSharedLib =
                         parsedPackage.getStaticSharedLibName() != null;
-                res.mRemovedInfo.mIsUpdate = true;
-                res.mRemovedInfo.mOrigUsers = installedUsers;
-                res.mRemovedInfo.mInstallReasons = new SparseArray<>(installedUsers.length);
+                removedInfo.mIsUpdate = true;
+                removedInfo.mOrigUsers = installedUsers;
+                removedInfo.mInstallReasons = new SparseIntArray(installedUsers.length);
                 for (int i = 0; i < installedUsers.length; i++) {
                     final int userId = installedUsers[i];
-                    res.mRemovedInfo.mInstallReasons.put(userId, ps.getInstallReason(userId));
+                    removedInfo.mInstallReasons.put(userId,
+                            ps.getInstallReason(userId));
                 }
-                res.mRemovedInfo.mUninstallReasons = new SparseArray<>(uninstalledUsers.length);
+                removedInfo.mUninstallReasons = new SparseIntArray(uninstalledUsers.length);
                 for (int i = 0; i < uninstalledUsers.length; i++) {
                     final int userId = uninstalledUsers[i];
-                    res.mRemovedInfo.mUninstallReasons.put(userId, ps.getUninstallReason(userId));
+                    removedInfo.mUninstallReasons.put(userId,
+                            ps.getUninstallReason(userId));
                 }
-                res.mRemovedInfo.mIsExternal = oldPackage.isExternalStorage();
+                removedInfo.mIsExternal = oldPackage.isExternalStorage();
+                request.setRemovedInfo(removedInfo);
 
                 sysPkg = oldPackage.isSystem();
                 if (sysPkg) {
@@ -1625,7 +1619,7 @@
                         Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
                                 + ", old=" + oldPackage);
                     }
-                    res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+                    request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
                     targetParseFlags = systemParseFlags;
                     targetScanFlags = systemScanFlags;
                 } else { // non system replace
@@ -1674,7 +1668,7 @@
                     oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
                     ps, disabledPs);
         } finally {
-            res.mFreezer = freezer;
+            request.setFreezer(freezer);
             if (shouldCloseFreezerBeforeReturn) {
                 freezer.close();
             }
@@ -1686,24 +1680,26 @@
      * scanned package should be updated to reflect the rename.
      */
     @GuardedBy("mPm.mInstallLock")
-    private void doRenameLI(InstallArgs args, int status, String statusMsg,
+    private void doRenameLI(InstallRequest request,
             ParsedPackage parsedPackage) throws PrepareFailure {
-        if (args.mMoveInfo != null) {
+        final int status = request.getReturnCode();
+        final String statusMsg = request.getReturnMsg();
+        if (request.isMoveInstall()) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
-                mRemovePackageHelper.cleanUpForMoveInstall(args.mMoveInfo.mToUuid,
-                        args.mMoveInfo.mPackageName, args.mMoveInfo.mFromCodePath);
+                mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
+                        request.getMovePackageName(), request.getMoveFromCodePath());
                 throw new PrepareFailure(status, statusMsg);
             }
             return;
         }
         // For file installations
         if (status != PackageManager.INSTALL_SUCCEEDED) {
-            mRemovePackageHelper.removeCodePath(args.mCodeFile);
+            mRemovePackageHelper.removeCodePath(request.getCodeFile());
             throw new PrepareFailure(status, statusMsg);
         }
 
-        final File targetDir = resolveTargetDir(args);
-        final File beforeCodeFile = args.mCodeFile;
+        final File targetDir = resolveTargetDir(request.getInstallFlags(), request.getCodeFile());
+        final File beforeCodeFile = request.getCodeFile();
         final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir,
                 parsedPackage.getPackageName());
 
@@ -1732,7 +1728,7 @@
         }
 
         // Reflect the rename internally
-        args.mCodeFile = afterCodeFile;
+        request.setCodeFile(afterCodeFile);
 
         // Reflect the rename in scanned details
         try {
@@ -1750,12 +1746,12 @@
 
     // TODO(b/168126411): Once staged install flow starts using the same folder as non-staged
     //  flow, we won't need this method anymore.
-    private File resolveTargetDir(InstallArgs args) {
-        boolean isStagedInstall = (args.mInstallFlags & INSTALL_STAGED) != 0;
+    private File resolveTargetDir(int installFlags, File codeFile) {
+        boolean isStagedInstall = (installFlags & INSTALL_STAGED) != 0;
         if (isStagedInstall) {
             return Environment.getDataAppDirectory(null);
         } else {
-            return args.mCodeFile.getParentFile();
+            return codeFile.getParentFile();
         }
     }
 
@@ -1905,7 +1901,7 @@
             final ScanRequest scanRequest = scanResult.mRequest;
             final ParsedPackage parsedPackage = scanRequest.mParsedPackage;
             final String packageName = parsedPackage.getPackageName();
-            final PackageInstalledInfo res = reconciledPkg.mInstallResult;
+            final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
             final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
 
@@ -1921,9 +1917,10 @@
                         .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers)
                         .setLastUpdateTime(System.currentTimeMillis());
 
-                res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                        mPm.snapshotComputer(), reconciledPkg.mPkgSetting, request.mAllUsers,
-                        mPm.mSettings.getPackagesLocked());
+                installRequest.getRemovedInfo().mBroadcastAllowList =
+                        mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
+                                reconciledPkg.mPkgSetting, request.mAllUsers,
+                                mPm.mSettings.getPackagesLocked());
                 if (reconciledPkg.mPrepareResult.mSystem) {
                     // Remove existing system package
                     removePackageHelper.removePackage(oldPackage, true);
@@ -1931,7 +1928,7 @@
                         // We didn't need to disable the .apk as a current system package,
                         // which means we are replacing another update that is already
                         // installed.  We need to make sure to delete the older one's .apk.
-                        res.mRemovedInfo.mArgs = new InstallArgs(
+                        installRequest.getRemovedInfo().mArgs = new InstallArgs(
                                 oldPackage.getPath(),
                                 getAppDexInstructionSets(
                                         AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
@@ -1939,7 +1936,7 @@
                                         AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
                                                 deletedPkgSetting)));
                     } else {
-                        res.mRemovedInfo.mArgs = null;
+                        installRequest.getRemovedInfo().mArgs = null;
                     }
                 } else {
                     try {
@@ -1957,7 +1954,7 @@
                     // Update the in-memory copy of the previous code paths.
                     PackageSetting ps1 = mPm.mSettings.getPackageLPr(
                             reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
-                    if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP)
+                    if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP)
                             == 0) {
                         Set<String> oldCodePaths = ps1.getOldCodePaths();
                         if (oldCodePaths == null) {
@@ -1970,12 +1967,11 @@
                         ps1.setOldCodePaths(null);
                     }
 
-                    if (reconciledPkg.mInstallResult.mReturnCode
-                            == PackageManager.INSTALL_SUCCEEDED) {
+                    if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
                         PackageSetting ps2 = mPm.mSettings.getPackageLPr(
                                 parsedPackage.getPackageName());
                         if (ps2 != null) {
-                            res.mRemovedInfo.mRemovedForAllUsers =
+                            installRequest.getRemovedInfo().mRemovedForAllUsers =
                                     mPm.mPackages.get(ps2.getPackageName()) == null;
                         }
                     }
@@ -1984,15 +1980,16 @@
 
             AndroidPackage pkg = commitReconciledScanResultLocked(
                     reconciledPkg, request.mAllUsers);
-            updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res);
+            updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, installRequest);
 
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
             if (ps != null) {
-                res.mNewUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true);
+                installRequest.setNewUsers(
+                        ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true));
                 ps.setUpdateAvailable(false /*updateAvailable*/);
             }
-            if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED) {
-                mPm.updateSequenceNumberLP(ps, res.mNewUsers);
+            if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+                mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
                 mPm.updateInstantAppInstallerLocked(packageName);
             }
         }
@@ -2005,20 +2002,18 @@
     }
 
     private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg,
-            int[] allUsers, PackageInstalledInfo res) {
-        updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, res);
+            int[] allUsers, InstallRequest installRequest) {
+        updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, installRequest);
     }
 
     private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg,
-            int[] allUsers, PackageInstalledInfo res) {
+            int[] allUsers, InstallRequest installRequest) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.getPackageName();
-        final int[] installedForUsers = res.mOrigUsers;
-        final InstallArgs installArgs = reconciledPkg.mInstallArgs;
-        final int installReason = installArgs.mInstallReason;
-        InstallSource installSource = installArgs.mInstallSource;
-        final String installerPackageName = installSource.installerPackageName;
+        final int[] installedForUsers = installRequest.getOriginUsers();
+        final int installReason = installRequest.getInstallReason();
+        final String installerPackageName = installRequest.getSourceInstallerPackageName();
 
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
         synchronized (mPm.mLock) {
@@ -2026,15 +2021,15 @@
             // of the package implies that the user actually wants to run that new code,
             // so we enable the package.
             final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
-            final int userId = installArgs.mUser.getIdentifier();
+            final int userId = installRequest.getUserId();
             if (ps != null) {
                 if (pkg.isSystem()) {
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
                     // Enable system package for requested users
-                    if (res.mOrigUsers != null) {
-                        for (int origUserId : res.mOrigUsers) {
+                    if (installedForUsers != null) {
+                        for (int origUserId : installedForUsers) {
                             if (userId == UserHandle.USER_ALL || userId == origUserId) {
                                 ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
                                         origUserId, installerPackageName);
@@ -2101,20 +2096,27 @@
                 // When replacing an existing package, preserve the original install reason for all
                 // users that had the package installed before. Similarly for uninstall reasons.
                 final Set<Integer> previousUserIds = new ArraySet<>();
-                if (res.mRemovedInfo != null && res.mRemovedInfo.mInstallReasons != null) {
-                    final int installReasonCount = res.mRemovedInfo.mInstallReasons.size();
+                if (installRequest.getRemovedInfo() != null
+                        && installRequest.getRemovedInfo().mInstallReasons != null) {
+                    final int installReasonCount =
+                            installRequest.getRemovedInfo().mInstallReasons.size();
                     for (int i = 0; i < installReasonCount; i++) {
-                        final int previousUserId = res.mRemovedInfo.mInstallReasons.keyAt(i);
+                        final int previousUserId =
+                                installRequest.getRemovedInfo().mInstallReasons.keyAt(i);
                         final int previousInstallReason =
-                                res.mRemovedInfo.mInstallReasons.valueAt(i);
+                                installRequest.getRemovedInfo().mInstallReasons.valueAt(i);
                         ps.setInstallReason(previousInstallReason, previousUserId);
                         previousUserIds.add(previousUserId);
                     }
                 }
-                if (res.mRemovedInfo != null && res.mRemovedInfo.mUninstallReasons != null) {
-                    for (int i = 0; i < res.mRemovedInfo.mUninstallReasons.size(); i++) {
-                        final int previousUserId = res.mRemovedInfo.mUninstallReasons.keyAt(i);
-                        final int previousReason = res.mRemovedInfo.mUninstallReasons.valueAt(i);
+                if (installRequest.getRemovedInfo() != null
+                        && installRequest.getRemovedInfo().mUninstallReasons != null) {
+                    for (int i = 0; i < installRequest.getRemovedInfo().mUninstallReasons.size();
+                            i++) {
+                        final int previousUserId =
+                                installRequest.getRemovedInfo().mUninstallReasons.keyAt(i);
+                        final int previousReason =
+                                installRequest.getRemovedInfo().mUninstallReasons.valueAt(i);
                         ps.setUninstallReason(previousReason, previousUserId);
                     }
                 }
@@ -2153,41 +2155,41 @@
                 final PermissionManagerServiceInternal.PackageInstalledParams.Builder
                         permissionParamsBuilder =
                         new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
-                final boolean grantPermissions = (installArgs.mInstallFlags
+                final boolean grantPermissions = (installRequest.getInstallFlags()
                         & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0;
                 if (grantPermissions) {
                     final List<String> grantedPermissions =
-                            installArgs.mInstallGrantPermissions != null
-                                    ? Arrays.asList(installArgs.mInstallGrantPermissions)
+                            installRequest.getInstallGrantPermissions() != null
+                                    ? Arrays.asList(installRequest.getInstallGrantPermissions())
                                     : pkg.getRequestedPermissions();
                     permissionParamsBuilder.setGrantedPermissions(grantedPermissions);
                 }
                 final boolean allowlistAllRestrictedPermissions =
-                        (installArgs.mInstallFlags
+                        (installRequest.getInstallFlags()
                                 & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
                 final List<String> allowlistedRestrictedPermissions =
                         allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions()
-                                : installArgs.mAllowlistedRestrictedPermissions;
+                                : installRequest.getAllowlistedRestrictedPermissions();
                 if (allowlistedRestrictedPermissions != null) {
                     permissionParamsBuilder.setAllowlistedRestrictedPermissions(
                             allowlistedRestrictedPermissions);
                 }
-                final int autoRevokePermissionsMode = installArgs.mAutoRevokePermissionsMode;
+                final int autoRevokePermissionsMode = installRequest.getAutoRevokePermissionsMode();
                 permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
                 final ScanResult scanResult = reconciledPkg.mScanResult;
                 mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId,
                         permissionParamsBuilder.build(), userId);
                 // Apply restricted settings on potentially dangerous packages.
-                if (installArgs.mPackageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
-                        || installArgs.mPackageSource
+                if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+                        || installRequest.getPackageSource()
                         == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
                     enableRestrictedSettings(pkgName, pkg.getUid());
                 }
             }
-            res.mName = pkgName;
-            res.mUid = pkg.getUid();
-            res.mPkg = pkg;
-            res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+            installRequest.setName(pkgName);
+            installRequest.setUid(pkg.getUid());
+            installRequest.setPkg(pkg);
+            installRequest.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
             //to update install status
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
             mPm.writeSettingsLPrTEMP();
@@ -2252,13 +2254,13 @@
             // can be used for optimizations.
             mArtManagerService.prepareAppProfiles(
                     pkg,
-                    mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()),
+                    mPm.resolveUserIds(reconciledPkg.mInstallRequest.getUserId()),
                     /* updateReferenceProfileContent= */ true);
 
             // Compute the compilation reason from the installation scenario.
             final int compilationReason =
                     mDexManager.getCompilationReasonForInstallScenario(
-                            reconciledPkg.mInstallArgs.mInstallScenario);
+                            reconciledPkg.mInstallRequest.getInstallScenario());
 
             // Construct the DexoptOptions early to see if we should skip running dexopt.
             //
@@ -2267,8 +2269,9 @@
             //
             // Also, don't fail application installs if the dexopt step fails.
             final boolean isBackupOrRestore =
-                    reconciledPkg.mInstallArgs.mInstallReason == INSTALL_REASON_DEVICE_RESTORE
-                            || reconciledPkg.mInstallArgs.mInstallReason
+                    reconciledPkg.mInstallRequest.getInstallReason()
+                            == INSTALL_REASON_DEVICE_RESTORE
+                            || reconciledPkg.mInstallRequest.getInstallReason()
                             == INSTALL_REASON_DEVICE_SETUP;
 
             final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
@@ -2580,35 +2583,31 @@
         }
     }
 
-    void handlePackagePostInstall(PackageInstalledInfo res, InstallArgs installArgs,
-            boolean launchedForRestore) {
+    void handlePackagePostInstall(InstallRequest request, boolean launchedForRestore) {
         final boolean killApp =
-                (installArgs.mInstallFlags & PackageManager.INSTALL_DONT_KILL_APP) == 0;
+                (request.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) == 0;
         final boolean virtualPreload =
-                ((installArgs.mInstallFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
-        final String installerPackage = installArgs.mInstallSource.installerPackageName;
-        final IPackageInstallObserver2 installObserver = installArgs.mObserver;
-        final int dataLoaderType = installArgs.mDataLoaderType;
-        final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED;
-        final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null;
-        final String packageName = res.mName;
+                ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+        final String installerPackage = request.getInstallerPackageName();
+        final int dataLoaderType = request.getDataLoaderType();
+        final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
+        final boolean update = request.isUpdate();
+        final String packageName = request.getName();
         final PackageStateInternal pkgSetting =
                 succeeded ? mPm.snapshotComputer().getPackageStateInternal(packageName) : null;
         final boolean removedBeforeUpdate = (pkgSetting == null)
                 || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals(
-                res.mPkg.getPath()));
+                request.getPkg().getPath()));
         if (succeeded && removedBeforeUpdate) {
             Slog.e(TAG, packageName + " was removed before handlePackagePostInstall "
                     + "could be executed");
-            res.mReturnCode = INSTALL_FAILED_PACKAGE_CHANGED;
-            res.mReturnMsg = "Package was removed before install could complete.";
+            request.setReturnCode(INSTALL_FAILED_PACKAGE_CHANGED);
+            request.setReturnMessage("Package was removed before install could complete.");
 
             // Remove the update failed package's older resources safely now
-            InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null;
-            if (args != null) {
-                mRemovePackageHelper.cleanUpResources(args);
-            }
-            mPm.notifyInstallObserver(res, installObserver);
+            mRemovePackageHelper.cleanUpResources(request.getOldCodeFile(),
+                    request.getOldInstructionSet());
+            mPm.notifyInstallObserver(request);
             return;
         }
 
@@ -2617,28 +2616,31 @@
             mPm.mPerUidReadTimeoutsCache = null;
 
             // Send the removed broadcasts
-            if (res.mRemovedInfo != null) {
-                if (res.mRemovedInfo.mIsExternal) {
+            if (request.getRemovedInfo() != null) {
+                if (request.getRemovedInfo().mIsExternal) {
                     if (DEBUG_INSTALL) {
-                        Slog.i(TAG, "upgrading pkg " + res.mRemovedInfo.mRemovedPackage
+                        Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage
                                 + " is ASEC-hosted -> UNAVAILABLE");
                     }
-                    final String[] pkgNames = new String[]{res.mRemovedInfo.mRemovedPackage};
-                    final int[] uids = new int[]{res.mRemovedInfo.mUid};
+                    final String[] pkgNames = new String[]{
+                            request.getRemovedInfo().mRemovedPackage};
+                    final int[] uids = new int[]{request.getRemovedInfo().mUid};
                     mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
                             false /* mediaStatus */, true /* replacing */, pkgNames, uids);
                 }
-                res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
+                request.getRemovedInfo().sendPackageRemovedBroadcasts(
+                        killApp, false /*removedBySystem*/);
             }
 
             final String installerPackageName =
-                    res.mInstallerPackageName != null
-                            ? res.mInstallerPackageName
-                            : res.mRemovedInfo != null
-                                    ? res.mRemovedInfo.mInstallerPackageName
+                    request.getInstallerPackageName() != null
+                            ? request.getInstallerPackageName()
+                            : request.getRemovedInfo() != null
+                                    ? request.getRemovedInfo().mInstallerPackageName
                                     : null;
 
-            mPm.notifyInstantAppPackageInstalled(res.mPkg.getPackageName(), res.mNewUsers);
+            mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(),
+                    request.getNewUsers());
 
             // Determine the set of users who are adding this package for
             // the first time vs. those who are seeing an update.
@@ -2646,8 +2648,9 @@
             int[] firstInstantUserIds = EMPTY_INT_ARRAY;
             int[] updateUserIds = EMPTY_INT_ARRAY;
             int[] instantUserIds = EMPTY_INT_ARRAY;
-            final boolean allNewUsers = res.mOrigUsers == null || res.mOrigUsers.length == 0;
-            for (int newUser : res.mNewUsers) {
+            final boolean allNewUsers = request.getOriginUsers() == null
+                    || request.getOriginUsers().length == 0;
+            for (int newUser : request.getNewUsers()) {
                 final boolean isInstantApp = pkgSetting.getUserStateOrDefault(newUser)
                         .isInstantApp();
                 if (allNewUsers) {
@@ -2659,7 +2662,7 @@
                     continue;
                 }
                 boolean isNew = true;
-                for (int origUser : res.mOrigUsers) {
+                for (int origUser : request.getOriginUsers()) {
                     if (origUser == newUser) {
                         isNew = false;
                         break;
@@ -2681,20 +2684,20 @@
             }
 
             // Send installed broadcasts if the package is not a static shared lib.
-            if (res.mPkg.getStaticSharedLibName() == null) {
-                mPm.mProcessLoggingHandler.invalidateBaseApkHash(res.mPkg.getBaseApkPath());
+            if (request.getPkg().getStaticSharedLibName() == null) {
+                mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
 
                 // Send added for users that see the package for the first time
                 // sendPackageAddedForNewUsers also deals with system apps
-                int appId = UserHandle.getAppId(res.mUid);
-                boolean isSystem = res.mPkg.isSystem();
+                int appId = UserHandle.getAppId(request.getUid());
+                boolean isSystem = request.getPkg().isSystem();
                 mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName,
                         isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
                         firstUserIds, firstInstantUserIds, dataLoaderType);
 
                 // Send added for users that don't see the package for the first time
                 Bundle extras = new Bundle();
-                extras.putInt(Intent.EXTRA_UID, res.mUid);
+                extras.putInt(Intent.EXTRA_UID, request.getUid());
                 if (update) {
                     extras.putBoolean(Intent.EXTRA_REPLACING, true);
                 }
@@ -2743,8 +2746,8 @@
                     mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                             packageName, extras, 0 /*flags*/,
                             null /*targetPackage*/, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
-                            null);
+                            updateUserIds, instantUserIds,
+                            request.getRemovedInfo().mBroadcastAllowList, null);
                     if (installerPackageName != null) {
                         mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
                                 extras, 0 /*flags*/,
@@ -2768,7 +2771,7 @@
                             null /*broadcastAllowList*/,
                             mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions(
                                     REASON_PACKAGE_REPLACED).toBundle());
-                } else if (launchedForRestore && !res.mPkg.isSystem()) {
+                } else if (launchedForRestore && !request.getPkg().isSystem()) {
                     // First-install and we did a restore, so we're responsible for the
                     // first-launch broadcast.
                     if (DEBUG_BACKUP) {
@@ -2780,17 +2783,17 @@
                 }
 
                 // Send broadcast package appeared if external for all users
-                if (res.mPkg.isExternalStorage()) {
+                if (request.getPkg().isExternalStorage()) {
                     if (!update) {
                         final StorageManager storageManager =
                                 mInjector.getSystemService(StorageManager.class);
                         VolumeInfo volume =
                                 storageManager.findVolumeByUuid(
                                         StorageManager.convert(
-                                                res.mPkg.getVolumeUuid()).toString());
+                                                request.getPkg().getVolumeUuid()).toString());
                         int packageExternalStorageType =
                                 PackageManagerServiceUtils.getPackageExternalStorageType(volume,
-                                        res.mPkg.isExternalStorage());
+                                        request.getPkg().isExternalStorage());
                         // If the package was installed externally, log it.
                         if (packageExternalStorageType != StorageEnums.UNKNOWN) {
                             FrameworkStatsLog.write(
@@ -2799,19 +2802,20 @@
                         }
                     }
                     if (DEBUG_INSTALL) {
-                        Slog.i(TAG, "upgrading pkg " + res.mPkg + " is external");
+                        Slog.i(TAG, "upgrading pkg " + request.getPkg() + " is external");
                     }
                     final String[] pkgNames = new String[]{packageName};
-                    final int[] uids = new int[]{res.mPkg.getUid()};
+                    final int[] uids = new int[]{request.getPkg().getUid()};
                     mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
                             true /* mediaStatus */, true /* replacing */, pkgNames, uids);
                 }
-            } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
+            } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib
                 // No need to kill consumers if it's installation of new version static shared lib.
                 final Computer snapshot = mPm.snapshotComputer();
-                final boolean dontKillApp = !update && res.mPkg.getStaticSharedLibName() != null;
-                for (int i = 0; i < res.mLibraryConsumers.size(); i++) {
-                    AndroidPackage pkg = res.mLibraryConsumers.get(i);
+                final boolean dontKillApp = !update
+                        && request.getPkg().getStaticSharedLibName() != null;
+                for (int i = 0; i < request.getLibraryConsumers().size(); i++) {
+                    AndroidPackage pkg = request.getLibraryConsumers().get(i);
                     // send broadcast that all consumers of the static shared library have changed
                     mPm.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), dontKillApp,
                             new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
@@ -2828,9 +2832,9 @@
             }
 
             if (allNewUsers && !update) {
-                mPm.notifyPackageAdded(packageName, res.mUid);
+                mPm.notifyPackageAdded(packageName, request.getUid());
             } else {
-                mPm.notifyPackageChanged(packageName, res.mUid);
+                mPm.notifyPackageChanged(packageName, request.getUid());
             }
 
             // Log current value of "unknown sources" setting
@@ -2838,7 +2842,8 @@
                     getUnknownSourcesSettings());
 
             // Remove the replaced package's older resources safely now
-            InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null;
+            InstallArgs args = request.getRemovedInfo() != null
+                    ? request.getRemovedInfo().mArgs : null;
             if (args != null) {
                 if (!killApp) {
                     // If we didn't kill the app, defer the deletion of code/resource files, since
@@ -2847,7 +2852,7 @@
                     // ApplicationInfo changes have propagated to all application threads.
                     mPm.scheduleDeferredNoKillPostDelete(args);
                 } else {
-                    mRemovePackageHelper.cleanUpResources(args);
+                    mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
                 }
             } else {
                 // Force a gc to clear up things. Ask for a background one, it's fine to go on
@@ -2874,21 +2879,21 @@
         final boolean deferInstallObserver = succeeded && update;
         if (deferInstallObserver) {
             if (killApp) {
-                mPm.scheduleDeferredPendingKillInstallObserver(res, installObserver);
+                mPm.scheduleDeferredPendingKillInstallObserver(request);
             } else {
-                mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+                mPm.scheduleDeferredNoKillInstallObserver(request);
             }
         } else {
-            mPm.notifyInstallObserver(res, installObserver);
+            mPm.notifyInstallObserver(request);
         }
 
         // Prune unused static shared libraries which have been cached a period of time
         mPm.schedulePruneUnusedStaticSharedLibraries(true /* delay */);
 
         // Log tracing if needed
-        if (installArgs.mTraceMethod != null) {
-            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, installArgs.mTraceMethod,
-                    installArgs.mTraceCookie);
+        if (request.getTraceMethod() != null) {
+            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, request.getTraceMethod(),
+                    request.getTraceCookie());
         }
     }
 
@@ -3936,10 +3941,10 @@
                             + "; " + pkgSetting.getPathString()
                             + " --> " + parsedPackage.getPath());
 
-            final InstallArgs args = new InstallArgs(
-                    pkgSetting.getPathString(), getAppDexInstructionSets(
-                    pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
-            mRemovePackageHelper.cleanUpResources(args);
+            mRemovePackageHelper.cleanUpResources(
+                    new File(pkgSetting.getPathString()),
+                    getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
+                            pkgSetting.getSecondaryCpuAbi()));
             synchronized (mPm.mLock) {
                 mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
             }
@@ -4021,10 +4026,9 @@
                                 + parsedPackage.getLongVersionCode()
                                 + "; " + pkgSetting.getPathString() + " --> "
                                 + parsedPackage.getPath());
-                InstallArgs args = new InstallArgs(
-                        pkgSetting.getPathString(), getAppDexInstructionSets(
-                        pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
-                mRemovePackageHelper.cleanUpResources(args);
+                mRemovePackageHelper.cleanUpResources(new File(pkgSetting.getPathString()),
+                        getAppDexInstructionSets(
+                                pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
             } else {
                 // The application on /system is older than the application on /data. Hide
                 // the application on /system and the version on /data will be scanned later
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 753d012..36bbf41 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,12 +16,389 @@
 
 package com.android.server.pm;
 
-final class InstallRequest {
-    public final InstallArgs mArgs;
-    public final PackageInstalledInfo mInstallResult;
+import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
 
-    InstallRequest(InstallArgs args, PackageInstalledInfo res) {
-        mArgs = args;
-        mInstallResult = res;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.apex.ApexInfo;
+import android.app.AppOpsManager;
+import android.content.pm.DataLoaderType;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.SigningDetails;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+
+import com.android.server.pm.pkg.AndroidPackage;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+final class InstallRequest {
+    private final int mUserId;
+    @Nullable
+    private final InstallArgs mInstallArgs;
+    @NonNull
+    private final PackageInstalledInfo mInstalledInfo;
+    @Nullable
+    private Runnable mPostInstallRunnable;
+    @Nullable
+    private PackageRemovedInfo mRemovedInfo;
+
+    // New install
+    InstallRequest(InstallingSession params) {
+        mUserId = params.getUser().getIdentifier();
+        mInstallArgs = new InstallArgs(params.mOriginInfo, params.mMoveInfo, params.mObserver,
+                params.mInstallFlags, params.mInstallSource, params.mVolumeUuid,
+                params.getUser(), null /*instructionSets*/, params.mPackageAbiOverride,
+                params.mGrantedRuntimePermissions, params.mAllowlistedRestrictedPermissions,
+                params.mAutoRevokePermissionsMode,
+                params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
+                params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
+                params.mDataLoaderType, params.mPackageSource);
+        mInstalledInfo = new PackageInstalledInfo();
+    }
+
+    // Install existing package as user
+    InstallRequest(int userId, int returnCode, AndroidPackage pkg, int[] newUsers,
+            Runnable runnable) {
+        mUserId = userId;
+        mInstallArgs = null;
+        mInstalledInfo = new PackageInstalledInfo();
+        mInstalledInfo.mReturnCode = returnCode;
+        mInstalledInfo.mPkg = pkg;
+        mInstalledInfo.mNewUsers = newUsers;
+        mPostInstallRunnable = runnable;
+    }
+
+    private static class PackageInstalledInfo {
+        String mName;
+        int mUid = -1;
+        // The set of users that originally had this package installed.
+        int[] mOrigUsers;
+        // The set of users that now have this package installed.
+        int[] mNewUsers;
+        AndroidPackage mPkg;
+        int mReturnCode;
+        String mReturnMsg;
+        String mInstallerPackageName;
+        // The set of packages consuming this shared library or null if no consumers exist.
+        ArrayList<AndroidPackage> mLibraryConsumers;
+        PackageFreezer mFreezer;
+        // In some error cases we want to convey more info back to the observer
+        String mOrigPackage;
+        String mOrigPermission;
+        // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install
+        ApexInfo mApexInfo;
+    }
+
+    public String getName() {
+        return mInstalledInfo.mName;
+    }
+
+    public String getReturnMsg() {
+        return mInstalledInfo.mReturnMsg;
+    }
+
+    public OriginInfo getOriginInfo() {
+        return mInstallArgs == null ? null : mInstallArgs.mOriginInfo;
+    }
+
+    public PackageRemovedInfo getRemovedInfo() {
+        return mRemovedInfo;
+    }
+
+    public String getOrigPackage() {
+        return mInstalledInfo.mOrigPackage;
+    }
+
+    public String getOrigPermission() {
+        return mInstalledInfo.mOrigPermission;
+    }
+
+    @Nullable
+    public File getCodeFile() {
+        return mInstallArgs == null ? null : mInstallArgs.mCodeFile;
+    }
+
+    @Nullable
+    public String getCodePath() {
+        return (mInstallArgs != null && mInstallArgs.mCodeFile != null)
+                ? mInstallArgs.mCodeFile.getAbsolutePath() : null;
+    }
+
+    @Nullable
+    public String getAbiOverride() {
+        return mInstallArgs == null ? null : mInstallArgs.mAbiOverride;
+    }
+
+    public int getReturnCode() {
+        return mInstalledInfo.mReturnCode;
+    }
+
+    @Nullable
+    public IPackageInstallObserver2 getObserver() {
+        return mInstallArgs == null ? null : mInstallArgs.mObserver;
+    }
+
+    public boolean isMoveInstall() {
+        return mInstallArgs != null && mInstallArgs.mMoveInfo != null;
+    }
+
+    @Nullable
+    public String getMoveToUuid() {
+        return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+                ? mInstallArgs.mMoveInfo.mToUuid : null;
+    }
+
+    @Nullable
+    public String getMovePackageName() {
+        return  (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+                ? mInstallArgs.mMoveInfo.mPackageName : null;
+    }
+
+    @Nullable
+    public String getMoveFromCodePath() {
+        return  (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+                ? mInstallArgs.mMoveInfo.mFromCodePath : null;
+    }
+
+    @Nullable
+    public File getOldCodeFile() {
+        return (mRemovedInfo != null && mRemovedInfo.mArgs != null)
+                ? mRemovedInfo.mArgs.mCodeFile : null;
+    }
+
+    @Nullable
+    public String[] getOldInstructionSet() {
+        return (mRemovedInfo != null && mRemovedInfo.mArgs != null)
+                ? mRemovedInfo.mArgs.mInstructionSets : null;
+    }
+
+    public UserHandle getUser() {
+        return new UserHandle(mUserId);
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public int getInstallFlags() {
+        return mInstallArgs == null ? 0 : mInstallArgs.mInstallFlags;
+    }
+
+    public int getInstallReason() {
+        return mInstallArgs == null ? INSTALL_REASON_UNKNOWN : mInstallArgs.mInstallReason;
+    }
+
+    @Nullable
+    public String getVolumeUuid() {
+        return mInstallArgs == null ? null : mInstallArgs.mVolumeUuid;
+    }
+
+    public AndroidPackage getPkg() {
+        return mInstalledInfo.mPkg;
+    }
+
+    @Nullable
+    public String getTraceMethod() {
+        return mInstallArgs == null ? null : mInstallArgs.mTraceMethod;
+    }
+
+    public int getTraceCookie() {
+        return mInstallArgs == null ? 0 : mInstallArgs.mTraceCookie;
+    }
+
+    public boolean isUpdate() {
+        return mRemovedInfo != null && mRemovedInfo.mRemovedPackage != null;
+    }
+
+    @Nullable
+    public String getRemovedPackage() {
+        return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null;
+    }
+
+    public boolean isInstallForExistingUser() {
+        return mInstallArgs == null;
+    }
+
+    @Nullable
+    public InstallSource getInstallSource() {
+        return mInstallArgs == null ? null : mInstallArgs.mInstallSource;
+    }
+
+    @Nullable
+    public String getInstallerPackageName() {
+        return (mInstallArgs != null && mInstallArgs.mInstallSource != null)
+                ? mInstallArgs.mInstallSource.installerPackageName : null;
+    }
+
+    public int getDataLoaderType() {
+        return mInstallArgs == null ? DataLoaderType.NONE : mInstallArgs.mDataLoaderType;
+    }
+
+    public int getSignatureSchemeVersion() {
+        return mInstallArgs == null ? SigningDetails.SignatureSchemeVersion.UNKNOWN
+                : mInstallArgs.mSigningDetails.getSignatureSchemeVersion();
+    }
+
+    @NonNull
+    public SigningDetails getSigningDetails() {
+        return mInstallArgs == null ? SigningDetails.UNKNOWN : mInstallArgs.mSigningDetails;
+    }
+
+    @Nullable
+    public Uri getOriginUri() {
+        return mInstallArgs == null ?  null : Uri.fromFile(mInstallArgs.mOriginInfo.mResolvedFile);
+    }
+
+    public ApexInfo getApexInfo() {
+        return mInstalledInfo.mApexInfo;
+    }
+
+    public String getSourceInstallerPackageName() {
+        return mInstallArgs.mInstallSource.installerPackageName;
+    }
+
+    public boolean isRollback() {
+        return mInstallArgs != null
+                && mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+    }
+
+    public int[] getNewUsers() {
+        return mInstalledInfo.mNewUsers;
+    }
+
+    public int[] getOriginUsers() {
+        return mInstalledInfo.mOrigUsers;
+    }
+
+    public int getUid() {
+        return mInstalledInfo.mUid;
+    }
+
+    @Nullable
+    public String[] getInstallGrantPermissions() {
+        return mInstallArgs == null ?  null : mInstallArgs.mInstallGrantPermissions;
+    }
+
+    public ArrayList<AndroidPackage> getLibraryConsumers() {
+        return mInstalledInfo.mLibraryConsumers;
+    }
+
+    @Nullable
+    public List<String> getAllowlistedRestrictedPermissions() {
+        return mInstallArgs == null ? null : mInstallArgs.mAllowlistedRestrictedPermissions;
+    }
+
+    public int getAutoRevokePermissionsMode() {
+        return mInstallArgs == null
+                ? AppOpsManager.MODE_DEFAULT : mInstallArgs.mAutoRevokePermissionsMode;
+    }
+
+    public int getPackageSource() {
+        return mInstallArgs == null
+                ? PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED : mInstallArgs.mPackageSource;
+    }
+
+    public int getInstallScenario() {
+        return mInstallArgs == null ? INSTALL_SCENARIO_DEFAULT : mInstallArgs.mInstallScenario;
+    }
+
+    public boolean isForceQueryableOverride() {
+        return mInstallArgs != null && mInstallArgs.mForceQueryableOverride;
+    }
+
+    public void closeFreezer() {
+        if (mInstalledInfo.mFreezer != null) {
+            mInstalledInfo.mFreezer.close();
+        }
+    }
+
+    public void runPostInstallRunnable() {
+        if (mPostInstallRunnable != null) {
+            mPostInstallRunnable.run();
+        }
+    }
+
+    public void setCodeFile(File codeFile) {
+        if (mInstallArgs != null) {
+            mInstallArgs.mCodeFile = codeFile;
+        }
+    }
+
+    public void setError(int code, String msg) {
+        setReturnCode(code);
+        setReturnMessage(msg);
+        Slog.w(TAG, msg);
+    }
+
+    public void setError(String msg, PackageManagerException e) {
+        mInstalledInfo.mReturnCode = e.error;
+        setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
+        Slog.w(TAG, msg, e);
+    }
+
+    public void setReturnCode(int returnCode) {
+        mInstalledInfo.mReturnCode = returnCode;
+    }
+
+    public void setReturnMessage(String returnMsg) {
+        mInstalledInfo.mReturnMsg = returnMsg;
+    }
+
+    public void setApexInfo(ApexInfo apexInfo) {
+        mInstalledInfo.mApexInfo = apexInfo;
+    }
+
+    public void setPkg(AndroidPackage pkg) {
+        mInstalledInfo.mPkg = pkg;
+    }
+
+    public void setUid(int uid) {
+        mInstalledInfo.mUid = uid;
+    }
+
+    public void setNewUsers(int[] newUsers) {
+        mInstalledInfo.mNewUsers = newUsers;
+    }
+
+    public void setOriginPackage(String originPackage) {
+        mInstalledInfo.mOrigPackage = originPackage;
+    }
+
+    public void setOriginPermission(String originPermission) {
+        mInstalledInfo.mOrigPermission = originPermission;
+    }
+
+    public void setInstallerPackageName(String installerPackageName) {
+        mInstalledInfo.mInstallerPackageName = installerPackageName;
+    }
+
+    public void setName(String packageName) {
+        mInstalledInfo.mName = packageName;
+    }
+
+    public void setOriginUsers(int[] userIds) {
+        mInstalledInfo.mOrigUsers = userIds;
+    }
+
+    public void setFreezer(PackageFreezer freezer) {
+        mInstalledInfo.mFreezer = freezer;
+    }
+
+    public void setRemovedInfo(PackageRemovedInfo removedInfo) {
+        mRemovedInfo = removedInfo;
+    }
+
+    public void setLibraryConsumers(ArrayList<AndroidPackage> libraryConsumers) {
+        mInstalledInfo.mLibraryConsumers = libraryConsumers;
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index a7f1727..8d5a5e1 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -253,68 +253,69 @@
     }
 
     private void processPendingInstall() {
-        InstallArgs args = new InstallArgs(this);
+        InstallRequest installRequest = new InstallRequest(this);
         if (mRet == PackageManager.INSTALL_SUCCEEDED) {
-            mRet = copyApk(args);
+            mRet = copyApk(installRequest);
         }
         if (mRet == PackageManager.INSTALL_SUCCEEDED) {
             F2fsUtils.releaseCompressedBlocks(
-                    mPm.mContext.getContentResolver(), new File(args.getCodePath()));
+                    mPm.mContext.getContentResolver(), new File(installRequest.getCodePath()));
         }
+        installRequest.setReturnCode(mRet);
         if (mParentInstallingSession != null) {
-            mParentInstallingSession.tryProcessInstallRequest(args, mRet);
+            mParentInstallingSession.tryProcessInstallRequest(installRequest);
         } else {
-            PackageInstalledInfo res = new PackageInstalledInfo(mRet);
             // Queue up an async operation since the package installation may take a little while.
             mPm.mHandler.post(() -> processInstallRequests(
-                    res.mReturnCode == PackageManager.INSTALL_SUCCEEDED /* success */,
-                    Collections.singletonList(new InstallRequest(args, res))));
+                    mRet == PackageManager.INSTALL_SUCCEEDED /* success */,
+                    Collections.singletonList(installRequest)));
         }
     }
 
-    private int copyApk(InstallArgs args) {
+    private int copyApk(InstallRequest request) {
         if (mMoveInfo == null) {
-            return copyApkForFileInstall(args);
+            return copyApkForFileInstall(request);
         } else {
-            return copyApkForMoveInstall(args);
+            return copyApkForMoveInstall(request);
         }
     }
 
-    private int copyApkForFileInstall(InstallArgs args) {
+    private int copyApkForFileInstall(InstallRequest request) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
         try {
             if (mOriginInfo.mStaged) {
                 if (DEBUG_INSTALL) {
                     Slog.d(TAG, mOriginInfo.mFile + " already staged; skipping copy");
                 }
-                args.mCodeFile = mOriginInfo.mFile;
+                request.setCodeFile(mOriginInfo.mFile);
                 return PackageManager.INSTALL_SUCCEEDED;
             }
 
             try {
                 final boolean isEphemeral =
                         (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
-                args.mCodeFile =
-                        mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral);
+                request.setCodeFile(
+                        mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral));
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to create copy file: " + e);
                 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
             }
 
             int ret = PackageManagerServiceUtils.copyPackage(
-                    mOriginInfo.mFile.getAbsolutePath(), args.mCodeFile);
+                    mOriginInfo.mFile.getAbsolutePath(), request.getCodeFile());
             if (ret != PackageManager.INSTALL_SUCCEEDED) {
                 Slog.e(TAG, "Failed to copy package");
                 return ret;
             }
 
-            final boolean isIncremental = isIncrementalPath(args.mCodeFile.getAbsolutePath());
-            final File libraryRoot = new File(args.mCodeFile, LIB_DIR_NAME);
+            final boolean isIncremental = isIncrementalPath(
+                    request.getCodeFile().getAbsolutePath());
+            final File libraryRoot = new File(request.getCodeFile(), LIB_DIR_NAME);
             NativeLibraryHelper.Handle handle = null;
             try {
-                handle = NativeLibraryHelper.Handle.create(args.mCodeFile);
+                handle = NativeLibraryHelper.Handle.create(request.getCodeFile());
                 ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                        args.mAbiOverride, isIncremental);
+                        request.getAbiOverride(), isIncremental);
             } catch (IOException e) {
                 Slog.e(TAG, "Copying native libraries failed", e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
@@ -328,7 +329,7 @@
         }
     }
 
-    private int copyApkForMoveInstall(InstallArgs args) {
+    private int copyApkForMoveInstall(InstallRequest request) {
         if (DEBUG_INSTALL) {
             Slog.d(TAG, "Moving " + mMoveInfo.mPackageName + " from "
                     + mMoveInfo.mFromUuid + " to " + mMoveInfo.mToUuid);
@@ -345,8 +346,9 @@
         }
 
         final String toPathName = new File(mMoveInfo.mFromCodePath).getName();
-        args.mCodeFile = new File(Environment.getDataAppDirectory(mMoveInfo.mToUuid), toPathName);
-        if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + args.mCodeFile);
+        request.setCodeFile(
+                new File(Environment.getDataAppDirectory(mMoveInfo.mToUuid), toPathName));
+        if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + request.getCodeFile());
 
         return PackageManager.INSTALL_SUCCEEDED;
     }
@@ -460,7 +462,7 @@
         List<InstallRequest> apexInstallRequests = new ArrayList<>();
         List<InstallRequest> apkInstallRequests = new ArrayList<>();
         for (InstallRequest request : installRequests) {
-            if ((request.mArgs.mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
+            if ((request.getInstallFlags() & PackageManager.INSTALL_APEX) != 0) {
                 apexInstallRequests.add(request);
             } else {
                 apkInstallRequests.add(request);
@@ -485,7 +487,7 @@
                 // processInstallRequestAsync. In that case just notify the observer about the
                 // failure.
                 InstallRequest request = apexInstallRequests.get(0);
-                mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver);
+                mPm.notifyInstallObserver(request);
             }
             return;
         }
@@ -496,28 +498,25 @@
     private void processApkInstallRequests(boolean success, List<InstallRequest> installRequests) {
         if (success) {
             for (InstallRequest request : installRequests) {
-                if (request.mInstallResult.mReturnCode != PackageManager.INSTALL_SUCCEEDED) {
-                    cleanUpForFailedInstall(request.mArgs);
+                if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
+                    cleanUpForFailedInstall(request);
                 }
             }
 
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
-                doPostInstall(request.mInstallResult.mReturnCode, request.mArgs);
+                doPostInstall(request);
             }
         }
         for (InstallRequest request : installRequests) {
-            mInstallPackageHelper.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
-                    request.mInstallResult,
-                    new PostInstallData(request.mArgs,
-                            request.mInstallResult, null));
+            mInstallPackageHelper.restoreAndPostInstall(request);
         }
     }
 
-    private void doPostInstall(int status, InstallArgs args) {
+    private void doPostInstall(InstallRequest request) {
         if (mMoveInfo != null) {
-            if (status == PackageManager.INSTALL_SUCCEEDED) {
+            if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
                 mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mFromUuid,
                         mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
             } else {
@@ -525,18 +524,18 @@
                         mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
             }
         } else {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                mRemovePackageHelper.removeCodePath(args.mCodeFile);
+            if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
+                mRemovePackageHelper.removeCodePath(request.getCodeFile());
             }
         }
     }
 
-    private void cleanUpForFailedInstall(InstallArgs args) {
-        if (args.mMoveInfo != null) {
-            mRemovePackageHelper.cleanUpForMoveInstall(args.mMoveInfo.mToUuid,
-                    args.mMoveInfo.mPackageName, args.mMoveInfo.mFromCodePath);
+    private void cleanUpForFailedInstall(InstallRequest request) {
+        if (request.isMoveInstall()) {
+            mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
+                    request.getMovePackageName(), request.getMoveFromCodePath());
         } else {
-            mRemovePackageHelper.removeCodePath(args.mCodeFile);
+            mRemovePackageHelper.removeCodePath(request.getCodeFile());
         }
     }
 
@@ -560,7 +559,7 @@
         InstallRequest request = requests.get(0);
         try {
             // Should directory scanning logic be moved to ApexManager for better test coverage?
-            final File dir = request.mArgs.mOriginInfo.mResolvedFile;
+            final File dir = request.getOriginInfo().mResolvedFile;
             final File[] apexes = dir.listFiles();
             if (apexes == null) {
                 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -580,7 +579,7 @@
                     // The newly installed APEX will not be reverted even if
                     // processApkInstallRequests() fails. Need a way to keep info stored in apexd
                     // and PMS in sync in the face of install failures.
-                    request.mInstallResult.mApexInfo = apexInfo;
+                    request.setApexInfo(apexInfo);
                     mPm.mHandler.post(() -> processApkInstallRequests(true, requests));
                     return;
                 } else {
@@ -588,10 +587,10 @@
                 }
             }
         } catch (PackageManagerException e) {
-            request.mInstallResult.setError("APEX installation failed", e);
+            request.setError("APEX installation failed", e);
         }
         PackageManagerService.invalidatePackageInfoCache();
-        mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver);
+        mPm.notifyInstallObserver(request);
     }
 
     /**
@@ -600,7 +599,7 @@
      */
     private class MultiPackageInstallingSession {
         private final List<InstallingSession> mChildInstallingSessions;
-        private final Map<InstallArgs, Integer> mCurrentState;
+        private final Map<InstallRequest, Integer> mCurrentState;
         @NonNull
         final PackageManagerService mPm;
         final UserHandle mUser;
@@ -636,8 +635,8 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        public void tryProcessInstallRequest(InstallArgs args, int currentStatus) {
-            mCurrentState.put(args, currentStatus);
+        public void tryProcessInstallRequest(InstallRequest request) {
+            mCurrentState.put(request, request.getReturnCode());
             if (mCurrentState.size() != mChildInstallingSessions.size()) {
                 return;
             }
@@ -651,9 +650,9 @@
                 }
             }
             final List<InstallRequest> installRequests = new ArrayList<>(mCurrentState.size());
-            for (Map.Entry<InstallArgs, Integer> entry : mCurrentState.entrySet()) {
-                installRequests.add(new InstallRequest(entry.getKey(),
-                        new PackageInstalledInfo(completeStatus)));
+            for (Map.Entry<InstallRequest, Integer> entry : mCurrentState.entrySet()) {
+                entry.getKey().setReturnCode(completeStatus);
+                installRequests.add(entry.getKey());
             }
             int finalCompleteStatus = completeStatus;
             mPm.mHandler.post(() -> processInstallRequests(
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 1ea4d62..4e8b0a1 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -89,18 +89,14 @@
             case POST_INSTALL: {
                 if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
 
-                PostInstallData data = mPm.mRunningInstalls.get(msg.arg1);
+                InstallRequest request = mPm.mRunningInstalls.get(msg.arg1);
                 final boolean didRestore = (msg.arg2 != 0);
                 mPm.mRunningInstalls.delete(msg.arg1);
 
-                if (data != null && data.res.mFreezer != null) {
-                    data.res.mFreezer.close();
-                }
-
-                if (data != null && data.mPostInstallRunnable != null) {
-                    data.mPostInstallRunnable.run();
-                } else if (data != null && data.args != null) {
-                    mInstallPackageHelper.handlePackagePostInstall(data.res, data.args, didRestore);
+                request.closeFreezer();
+                request.runPostInstallRunnable();
+                if (!request.isInstallForExistingUser()) {
+                    mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
                 } else if (DEBUG_INSTALL) {
                     // No post-install when we run restore from installExistingPackageForUser
                     Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1);
@@ -111,7 +107,7 @@
             case DEFERRED_NO_KILL_POST_DELETE: {
                 InstallArgs args = (InstallArgs) msg.obj;
                 if (args != null) {
-                    mRemovePackageHelper.cleanUpResources(args);
+                    mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
                 }
             } break;
             case DEFERRED_NO_KILL_INSTALL_OBSERVER:
diff --git a/services/core/java/com/android/server/pm/PackageInstalledInfo.java b/services/core/java/com/android/server/pm/PackageInstalledInfo.java
deleted file mode 100644
index 247abf3..0000000
--- a/services/core/java/com/android/server/pm/PackageInstalledInfo.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static com.android.server.pm.PackageManagerService.TAG;
-
-import android.apex.ApexInfo;
-import android.util.ExceptionUtils;
-import android.util.Slog;
-
-import com.android.server.pm.pkg.AndroidPackage;
-
-import java.util.ArrayList;
-
-final class PackageInstalledInfo {
-    String mName;
-    int mUid;
-    // The set of users that originally had this package installed.
-    int[] mOrigUsers;
-    // The set of users that now have this package installed.
-    int[] mNewUsers;
-    AndroidPackage mPkg;
-    int mReturnCode;
-    String mReturnMsg;
-    String mInstallerPackageName;
-    PackageRemovedInfo mRemovedInfo;
-    // The set of packages consuming this shared library or null if no consumers exist.
-    ArrayList<AndroidPackage> mLibraryConsumers;
-    PackageFreezer mFreezer;
-
-    // In some error cases we want to convey more info back to the observer
-    String mOrigPackage;
-    String mOrigPermission;
-
-    // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install
-    ApexInfo mApexInfo;
-
-    PackageInstalledInfo(int currentStatus) {
-        mReturnCode = currentStatus;
-        mUid = -1;
-        mPkg = null;
-        mRemovedInfo = null;
-    }
-
-    public void setError(int code, String msg) {
-        setReturnCode(code);
-        setReturnMessage(msg);
-        Slog.w(TAG, msg);
-    }
-
-    public void setError(String msg, PackageManagerException e) {
-        mReturnCode = e.error;
-        setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
-        Slog.w(TAG, msg, e);
-    }
-
-    public void setReturnCode(int returnCode) {
-        mReturnCode = returnCode;
-    }
-
-    private void setReturnMessage(String returnMsg) {
-        mReturnMsg = returnMsg;
-    }
-}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 66a97b3..218d9d1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1344,9 +1344,14 @@
         }
 
         private String getDeviceOwnerDeletedPackageMsg() {
-            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-            return dpm.getResources().getString(PACKAGE_DELETED_BY_DO,
-                    () -> mContext.getString(R.string.package_deleted_device_owner));
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+                return dpm.getResources().getString(PACKAGE_DELETED_BY_DO,
+                        () -> mContext.getString(R.string.package_deleted_device_owner));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 259202f..39a6eb7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -82,7 +82,6 @@
 import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageLoadingProgressCallback;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
@@ -826,10 +825,10 @@
     @Watched(manual = true)
     private final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
 
-    private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
+    private final Map<String, InstallRequest>
             mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
 
-    private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
+    private final Map<String, InstallRequest>
             mPendingKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
 
     // Internal interface for permission manager
@@ -908,7 +907,7 @@
     // Stores a list of users whose package restrictions file needs to be updated
     final ArraySet<Integer> mDirtyUsers = new ArraySet<>();
 
-    final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<>();
+    final SparseArray<InstallRequest> mRunningInstalls = new SparseArray<>();
     int mNextInstallToken = 1;  // nonzero; will be wrapped back to 1 when ++ overflows
 
     final @NonNull String[] mRequiredVerifierPackages;
@@ -1155,32 +1154,30 @@
     }
 
     void notifyInstallObserver(String packageName, boolean killApp) {
-        final Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
+        final InstallRequest installRequest =
                 killApp ? mPendingKillInstallObservers.remove(packageName)
                         : mNoKillInstallObservers.remove(packageName);
 
-        if (pair != null) {
-            notifyInstallObserver(pair.first, pair.second);
+        if (installRequest != null) {
+            notifyInstallObserver(installRequest);
         }
     }
 
-    void notifyInstallObserver(PackageInstalledInfo info,
-            IPackageInstallObserver2 installObserver) {
-        if (installObserver != null) {
+    void notifyInstallObserver(InstallRequest request) {
+        if (request.getObserver() != null) {
             try {
-                Bundle extras = extrasForInstallResult(info);
-                installObserver.onPackageInstalled(info.mName, info.mReturnCode,
-                        info.mReturnMsg, extras);
+                Bundle extras = extrasForInstallResult(request);
+                request.getObserver().onPackageInstalled(request.getName(),
+                        request.getReturnCode(), request.getReturnMsg(), extras);
             } catch (RemoteException e) {
                 Slog.i(TAG, "Observer no longer exists.");
             }
         }
     }
 
-    void scheduleDeferredNoKillInstallObserver(PackageInstalledInfo info,
-            IPackageInstallObserver2 observer) {
-        String packageName = info.mPkg.getPackageName();
-        mNoKillInstallObservers.put(packageName, Pair.create(info, observer));
+    void scheduleDeferredNoKillInstallObserver(InstallRequest request) {
+        String packageName = request.getPkg().getPackageName();
+        mNoKillInstallObservers.put(packageName, request);
         Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_INSTALL_OBSERVER, packageName);
         mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
     }
@@ -1196,10 +1193,9 @@
                 delay ? getPruneUnusedSharedLibrariesDelay() : 0);
     }
 
-    void scheduleDeferredPendingKillInstallObserver(PackageInstalledInfo info,
-            IPackageInstallObserver2 observer) {
-        final String packageName = info.mPkg.getPackageName();
-        mPendingKillInstallObservers.put(packageName, Pair.create(info, observer));
+    void scheduleDeferredPendingKillInstallObserver(InstallRequest request) {
+        final String packageName = request.getPkg().getPackageName();
+        mPendingKillInstallObservers.put(packageName, request);
         final Message message = mHandler.obtainMessage(DEFERRED_PENDING_KILL_INSTALL_OBSERVER,
                 packageName);
         mHandler.sendMessageDelayed(message, DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS);
@@ -1312,21 +1308,22 @@
         }
     }
 
-    private static Bundle extrasForInstallResult(PackageInstalledInfo res) {
+    private static Bundle extrasForInstallResult(InstallRequest request) {
         Bundle extras = null;
-        switch (res.mReturnCode) {
+        switch (request.getReturnCode()) {
             case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: {
                 extras = new Bundle();
                 extras.putString(PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION,
-                        res.mOrigPermission);
+                        request.getOrigPermission());
                 extras.putString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE,
-                        res.mOrigPackage);
+                        request.getOrigPackage());
                 break;
             }
             case PackageManager.INSTALL_SUCCEEDED: {
                 extras = new Bundle();
                 extras.putBoolean(Intent.EXTRA_REPLACING,
-                        res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null);
+                        request.getRemovedInfo() != null
+                                && request.getRemovedInfo().mRemovedPackage != null);
                 break;
             }
         }
@@ -3111,14 +3108,14 @@
         // state for observers to see the FIRST_LAUNCH signal.
         mHandler.post(() -> {
             for (int i = 0; i < mRunningInstalls.size(); i++) {
-                final PostInstallData data = mRunningInstalls.valueAt(i);
-                if (data.res.mReturnCode != PackageManager.INSTALL_SUCCEEDED) {
+                final InstallRequest installRequest = mRunningInstalls.valueAt(i);
+                if (installRequest.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
                     continue;
                 }
-                if (packageName.equals(data.res.mPkg.getPackageName())) {
+                if (packageName.equals(installRequest.getPkg().getPackageName())) {
                     // right package; but is it for the right user?
-                    for (int uIndex = 0; uIndex < data.res.mNewUsers.length; uIndex++) {
-                        if (userId == data.res.mNewUsers[uIndex]) {
+                    for (int uIndex = 0; uIndex < installRequest.getNewUsers().length; uIndex++) {
+                        if (userId == installRequest.getNewUsers()[uIndex]) {
                             if (DEBUG_BACKUP) {
                                 Slog.i(TAG, "Package " + packageName
                                         + " being restored so deferring FIRST_LAUNCH");
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 28ad4b6..3c863d0 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.PowerExemptionManager;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
@@ -42,8 +43,8 @@
     int[] mRemovedUsers = null;
     int[] mBroadcastUsers = null;
     int[] mInstantUserIds = null;
-    SparseArray<Integer> mInstallReasons;
-    SparseArray<Integer> mUninstallReasons;
+    SparseIntArray mInstallReasons;
+    SparseIntArray mUninstallReasons;
     boolean mIsRemovedPackageSystemUpdate = false;
     boolean mIsUpdate;
     boolean mDataRemoved;
diff --git a/services/core/java/com/android/server/pm/PostInstallData.java b/services/core/java/com/android/server/pm/PostInstallData.java
deleted file mode 100644
index 65d2a65..0000000
--- a/services/core/java/com/android/server/pm/PostInstallData.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-/**
- * Record-keeping of restore-after-install operations that are currently in flight
- * between the Package Manager and the Backup Manager
- */
-public final class PostInstallData {
-    @Nullable
-    public final InstallArgs args;
-    @NonNull
-    public final PackageInstalledInfo res;
-    @Nullable
-    public final Runnable mPostInstallRunnable;
-
-    PostInstallData(@Nullable InstallArgs args, @NonNull PackageInstalledInfo res,
-            @Nullable Runnable postInstallRunnable) {
-        this.args = args;
-        this.res = res;
-        mPostInstallRunnable = postInstallRunnable;
-    }
-}
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 0f7c652..165b450 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -77,11 +77,10 @@
             }
 
             // the following may be null if we're just reconciling on boot (and not during install)
-            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
-            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
+            final InstallRequest installRequest = request.mInstallRequests.get(installPackageName);
             final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
-            final boolean isInstall = installArgs != null;
-            if (isInstall && (res == null || prepareResult == null)) {
+            final boolean isInstall = installRequest != null;
+            if (isInstall && prepareResult == null) {
                 throw new ReconcileFailure("Reconcile arguments are not balanced for "
                         + installPackageName + "!");
             }
@@ -92,7 +91,8 @@
                 final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
                 final int deleteFlags = PackageManager.DELETE_KEEP_DATA
                         | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
-                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
+                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(
+                        installRequest.getRemovedInfo(),
                         prepareResult.mOriginalPs, prepareResult.mDisabledPs,
                         deleteFlags, null /* all users */);
                 if (deletePackageAction == null) {
@@ -146,8 +146,8 @@
                             request.mVersionInfos.get(installPackageName);
                     final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
                     final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
-                    final boolean isRollback = installArgs != null
-                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+                    final boolean isRollback = installRequest != null
+                            && installRequest.isRollback();
                     final boolean compatMatch =
                             PackageManagerServiceUtils.verifySignatures(signatureCheckPs,
                                     sharedUserSetting, disabledPkgSetting,
@@ -257,8 +257,8 @@
             }
 
             result.put(installPackageName,
-                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
-                            res, request.mPreparedPackages.get(installPackageName), scanResult,
+                    new ReconciledPackage(request, installRequest, scanResult.mPkgSetting,
+                            request.mPreparedPackages.get(installPackageName), scanResult,
                             deletePackageAction, allowedSharedLibInfos, signingDetails,
                             sharedUserSignaturesChanged, removeAppKeySetData));
         }
diff --git a/services/core/java/com/android/server/pm/ReconcileRequest.java b/services/core/java/com/android/server/pm/ReconcileRequest.java
index 84b292f..3568c15 100644
--- a/services/core/java/com/android/server/pm/ReconcileRequest.java
+++ b/services/core/java/com/android/server/pm/ReconcileRequest.java
@@ -34,20 +34,17 @@
     public final Map<String, ScanResult> mScannedPackages;
 
     public final Map<String, AndroidPackage> mAllPackages;
-    public final Map<String, InstallArgs> mInstallArgs;
-    public final Map<String, PackageInstalledInfo> mInstallResults;
+    public final Map<String, InstallRequest> mInstallRequests;
     public final Map<String, PrepareResult> mPreparedPackages;
     public final Map<String, Settings.VersionInfo> mVersionInfos;
 
     ReconcileRequest(Map<String, ScanResult> scannedPackages,
-            Map<String, InstallArgs> installArgs,
-            Map<String, PackageInstalledInfo> installResults,
+            Map<String, InstallRequest> installRequests,
             Map<String, PrepareResult> preparedPackages,
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos) {
         mScannedPackages = scannedPackages;
-        mInstallArgs = installArgs;
-        mInstallResults = installResults;
+        mInstallRequests = installRequests;
         mPreparedPackages = preparedPackages;
         mAllPackages = allPackages;
         mVersionInfos = versionInfos;
@@ -56,7 +53,7 @@
     ReconcileRequest(Map<String, ScanResult> scannedPackages,
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos) {
-        this(scannedPackages, Collections.emptyMap(), Collections.emptyMap(),
+        this(scannedPackages, Collections.emptyMap(),
                 Collections.emptyMap(), allPackages, versionInfos);
     }
 }
diff --git a/services/core/java/com/android/server/pm/ReconciledPackage.java b/services/core/java/com/android/server/pm/ReconciledPackage.java
index 3bb5a1b..d4da6c7 100644
--- a/services/core/java/com/android/server/pm/ReconciledPackage.java
+++ b/services/core/java/com/android/server/pm/ReconciledPackage.java
@@ -37,9 +37,8 @@
     public final PackageSetting mPkgSetting;
     public final ScanResult mScanResult;
     // TODO: Remove install-specific details from the reconcile result
-    public final PackageInstalledInfo mInstallResult;
     @Nullable public final PrepareResult mPrepareResult;
-    @Nullable public final InstallArgs mInstallArgs;
+    @Nullable public final InstallRequest mInstallRequest;
     public final DeletePackageAction mDeletePackageAction;
     public final List<SharedLibraryInfo> mAllowedSharedLibraryInfos;
     public final SigningDetails mSigningDetails;
@@ -48,9 +47,8 @@
     public final boolean mRemoveAppKeySetData;
 
     ReconciledPackage(ReconcileRequest request,
-            InstallArgs installArgs,
+            InstallRequest installRequest,
             PackageSetting pkgSetting,
-            PackageInstalledInfo installResult,
             PrepareResult prepareResult,
             ScanResult scanResult,
             DeletePackageAction deletePackageAction,
@@ -59,9 +57,8 @@
             boolean sharedUserSignaturesChanged,
             boolean removeAppKeySetData) {
         mRequest = request;
-        mInstallArgs = installArgs;
+        mInstallRequest = installRequest;
         mPkgSetting = pkgSetting;
-        mInstallResult = installResult;
         mPrepareResult = prepareResult;
         mScanResult = scanResult;
         mDeletePackageAction = deletePackageAction;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index a025965..55d4b36 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -385,29 +385,29 @@
         }
     }
 
-    void cleanUpResources(InstallArgs args) {
+    void cleanUpResources(File codeFile, String[] instructionSets) {
         synchronized (mPm.mInstallLock) {
-            cleanUpResourcesLI(args);
+            cleanUpResourcesLI(codeFile, instructionSets);
         }
     }
 
     // Need installer lock especially for dex file removal.
     @GuardedBy("mPm.mInstallLock")
-    private void cleanUpResourcesLI(InstallArgs args) {
+    private void cleanUpResourcesLI(File codeFile, String[] instructionSets) {
         // Try enumerating all code paths before deleting
         List<String> allCodePaths = Collections.EMPTY_LIST;
-        if (args.mCodeFile != null && args.mCodeFile.exists()) {
+        if (codeFile != null && codeFile.exists()) {
             final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
             final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
-                    input.reset(), args.mCodeFile, /* flags */ 0);
+                    input.reset(), codeFile, /* flags */ 0);
             if (result.isSuccess()) {
                 // Ignore error; we tried our best
                 allCodePaths = result.getResult().getAllApkPaths();
             }
         }
 
-        removeCodePathLI(args.mCodeFile);
-        removeDexFilesLI(allCodePaths, args.mInstructionSets);
+        removeCodePathLI(codeFile);
+        removeDexFilesLI(allCodePaths, instructionSets);
     }
 
     @GuardedBy("mPm.mInstallLock")
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 073776f..37abeac 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -781,7 +781,7 @@
             for (String voiceInteractPackageName : voiceInteractPackageNames) {
                 grantPermissionsToSystemPackage(pm, voiceInteractPackageName, userId,
                         CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS,
-                        PHONE_PERMISSIONS, SMS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
+                        PHONE_PERMISSIONS, SMS_PERMISSIONS, COARSE_BACKGROUND_LOCATION_PERMISSIONS,
                         NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS);
             }
         }
diff --git a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
index 1fb7cf5..421f7ce 100644
--- a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
@@ -260,10 +260,11 @@
     private void suggestGnssTime(LocationTime locationTime) {
         logDebug("suggestGnssTime()");
 
-        long gnssTime = locationTime.getTime();
+        long gnssUnixEpochTimeMillis = locationTime.getUnixEpochTimeMillis();
         long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L;
 
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(elapsedRealtimeMs, gnssTime);
+        TimestampedValue<Long> timeSignal =
+                new TimestampedValue<>(elapsedRealtimeMs, gnssUnixEpochTimeMillis);
         mLastSuggestedGnssTime = timeSignal;
 
         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
index 41373cd..09ba195 100644
--- a/services/core/java/com/android/server/utils/AlarmQueue.java
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -64,8 +64,11 @@
      * The pair is the key and its associated alarm time (in the elapsed realtime timebase).
      */
     private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> {
+        private static final Comparator<Pair<?, Long>> sTimeComparator =
+                (o1, o2) -> Long.compare(o1.second, o2.second);
+
         AlarmPriorityQueue() {
-            super(1, Comparator.comparingLong(o -> o.second));
+            super(1, sTimeComparator);
         }
 
         /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1c583be..0637ed3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2755,6 +2755,7 @@
         }
 
         final StartingSurfaceController.StartingSurface surface;
+        final WindowState startingWindow = mStartingWindow;
         final boolean animate;
         if (mStartingData != null) {
             animate = prepareAnimation && mStartingData.needRevealAnimation()
@@ -2779,7 +2780,19 @@
             return;
         }
 
-        surface.remove(animate);
+        if (animate && mTransitionController.inCollectingTransition(startingWindow)
+                && startingWindow.cancelAndRedraw()) {
+            // Defer remove starting window after transition start.
+            // If splash screen window was in collecting, the client side is unable to draw because
+            // of Session#cancelDraw, which will blocking the remove animation.
+            startingWindow.mSyncTransaction.addTransactionCommittedListener(Runnable::run, () -> {
+                synchronized (mAtmService.mGlobalLock) {
+                    surface.remove(true);
+                }
+            });
+        } else {
+            surface.remove(animate);
+        }
     }
 
     /**
@@ -8033,11 +8046,7 @@
         // Horizontal position
         int offsetX = 0;
         if (parentBounds.width() != screenResolvedBounds.width()) {
-            if (screenResolvedBounds.width() >= parentAppBounds.width()) {
-                // If resolved bounds overlap with insets, center within app bounds.
-                offsetX = getCenterOffset(
-                        parentAppBounds.width(), screenResolvedBounds.width());
-            } else {
+            if (screenResolvedBounds.width() <= parentAppBounds.width()) {
                 float positionMultiplier =
                         mLetterboxUiController.getHorizontalPositionMultiplier(
                                 newParentConfiguration);
@@ -8049,11 +8058,7 @@
         // Vertical position
         int offsetY = 0;
         if (parentBounds.height() != screenResolvedBounds.height()) {
-            if (screenResolvedBounds.height() >= parentAppBounds.height()) {
-                // If resolved bounds overlap with insets, center within app bounds.
-                offsetY = getCenterOffset(
-                        parentAppBounds.height(), screenResolvedBounds.height());
-            } else {
+            if (screenResolvedBounds.height() <= parentAppBounds.height()) {
                 float positionMultiplier =
                         mLetterboxUiController.getVerticalPositionMultiplier(
                                 newParentConfiguration);
@@ -8071,6 +8076,15 @@
             offsetBounds(resolvedConfig, offsetX, offsetY);
         }
 
+        // If the top is aligned with parentAppBounds add the vertical insets back so that the app
+        // content aligns with the status bar
+        if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) {
+            resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top;
+            if (mSizeCompatBounds != null) {
+                mSizeCompatBounds.top = parentBounds.top;
+            }
+        }
+
         // Since bounds has changed, the configuration needs to be computed accordingly.
         getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
     }
@@ -8448,7 +8462,7 @@
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
         final boolean fillContainer = resolvedBounds.equals(containingBounds);
         final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
-        final int screenPosY  = fillContainer ? containerBounds.top : containerAppBounds.top;
+        final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
 
         if (screenPosX != 0 || screenPosY != 0) {
             if (mSizeCompatBounds != null) {
@@ -8794,24 +8808,22 @@
         // Also account for the insets (e.g. display cutouts, navigation bar), which will be
         // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out
         // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise,
-        // the app bounds would end up too small.
+        // the app bounds would end up too small. To achieve this we will also add clippable insets
+        // when the corresponding dimension fully fills the parent
+
         int right = activityWidth + containingAppBounds.left;
+        int left = containingAppBounds.left;
         if (right >= containingAppBounds.right) {
-            right += containingBounds.right - containingAppBounds.right;
+            right = containingBounds.right;
+            left = containingBounds.left;
         }
         int bottom = activityHeight + containingAppBounds.top;
+        int top = containingAppBounds.top;
         if (bottom >= containingAppBounds.bottom) {
-            bottom += containingBounds.bottom - containingAppBounds.bottom;
+            bottom = containingBounds.bottom;
+            top = containingBounds.top;
         }
-        outBounds.set(containingBounds.left, containingBounds.top, right, bottom);
-
-        // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
-        // container app bounds. Otherwise the entire container bounds are available.
-        if (!outBounds.equals(containingBounds)) {
-            // The horizontal position should not cover insets (e.g. display cutout).
-            outBounds.left = containingAppBounds.left;
-        }
-
+        outBounds.set(left, top, right, bottom);
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 7e62c61..2ea6a3f 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -467,16 +467,6 @@
             return TRANSIT_OLD_WALLPAPER_OPEN;
         }
 
-        // Some devices don't show a wallpaper. In that case we should still trigger wallpaper
-        // transitions when animating to/from the home activity
-        if (wallpaperTarget == null) {
-            if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) {
-                return TRANSIT_OLD_WALLPAPER_OPEN;
-            } else if (topClosingApp != null && topClosingApp.isActivityTypeHome()) {
-                return TRANSIT_OLD_WALLPAPER_CLOSE;
-            }
-        }
-
         final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
                 openingApps, closingApps, true /* visible */);
         final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
@@ -488,6 +478,11 @@
         @TransitContainerType int openingType = getTransitContainerType(openingContainer);
         @TransitContainerType int closingType = getTransitContainerType(closingContainer);
         if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) {
+            if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) {
+                // If we are opening the home task, we want to play an animation as if
+                // the task on top is being brought to back.
+                return TRANSIT_OLD_TASK_TO_BACK;
+            }
             return TRANSIT_OLD_TASK_TO_FRONT;
         }
         if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) {
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 219092b..1266db5 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -357,7 +357,12 @@
      * or seamless transformation in a rotated display.
      */
     boolean shouldFreezeInsetsPosition(WindowState w) {
-        return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition()
+        if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+            // Expect a screenshot layer has covered the screen, so it is fine to let client side
+            // insets animation runner update the position directly.
+            return false;
+        }
+        return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted
                 && isTargetToken(w.mToken);
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index f3bd1a1..2de8faf 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -64,6 +64,7 @@
 import android.view.InternalInsetsAnimationController;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
@@ -398,16 +399,13 @@
 
         if (WindowConfiguration.isFloating(windowingMode)
                 || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
+            // Keep frames, caption, and IME.
+            int types = WindowInsets.Type.captionBar();
+            if (windowingMode != WINDOWING_MODE_PINNED) {
+                types |= WindowInsets.Type.ime();
+            }
             InsetsState newState = new InsetsState();
-
-            // Only caption and IME are needed.
-            if (state.peekSource(ITYPE_CAPTION_BAR) != null) {
-                newState.addSource(state.peekSource(ITYPE_CAPTION_BAR));
-            }
-            if (windowingMode != WINDOWING_MODE_PINNED && state.peekSource(ITYPE_IME) != null) {
-                newState.addSource(state.peekSource(ITYPE_IME));
-            }
-
+            newState.set(state, types);
             state = newState;
             stateCopied = true;
         }
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 27550d9..3d00686 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -273,8 +273,8 @@
         @Override
         public boolean onDoubleTapEvent(MotionEvent e) {
             if (e.getAction() == MotionEvent.ACTION_UP) {
-                mDoubleTapCallbackX.accept((int) e.getX());
-                mDoubleTapCallbackY.accept((int) e.getY());
+                mDoubleTapCallbackX.accept((int) e.getRawX());
+                mDoubleTapCallbackY.accept((int) e.getRawY());
                 return true;
             }
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 88c47db..9722bb6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9277,4 +9277,38 @@
                         "Unexpected letterbox background type: " + letterboxBackgroundType);
         }
     }
+
+    @Override
+    public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
+            ScreenCapture.ScreenCaptureListener listener) {
+        Slog.d(TAG, "captureDisplay");
+        if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+
+        final SurfaceControl displaySurfaceControl;
+        synchronized (mGlobalLock) {
+            DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException("Trying to screenshot and invalid display: "
+                        + displayId);
+            }
+
+            displaySurfaceControl = displayContent.getSurfaceControl();
+
+            if (captureArgs == null) {
+                displayContent.getBounds(mTmpRect);
+                mTmpRect.offsetTo(0, 0);
+                captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+                        .setSourceCrop(mTmpRect)
+                        .build();
+            }
+        }
+
+        ScreenCapture.LayerCaptureArgs args =
+                new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs)
+                        .build();
+
+        ScreenCapture.captureLayers(args, listener);
+    }
 }
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index daca153..b7a4fd1 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -37,6 +37,7 @@
     KEYBOARD,
     MOUSE,
     TOUCHSCREEN,
+    DPAD,
 };
 
 enum class UinputAction {
@@ -76,6 +77,13 @@
         {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM},
 };
 
+// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices
+static std::map<int, int> DPAD_KEY_CODE_MAPPING = {
+        {AKEYCODE_DPAD_DOWN, KEY_DOWN},     {AKEYCODE_DPAD_UP, KEY_UP},
+        {AKEYCODE_DPAD_LEFT, KEY_LEFT},     {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
+        {AKEYCODE_DPAD_CENTER, KEY_SELECT},
+};
+
 // Keycode mapping from https://source.android.com/devices/input/keyboard-devices
 static std::map<int, int> KEY_CODE_MAPPING = {
         {AKEYCODE_0, KEY_0},
@@ -200,8 +208,13 @@
     ioctl(fd, UI_SET_EVBIT, EV_KEY);
     ioctl(fd, UI_SET_EVBIT, EV_SYN);
     switch (deviceType) {
+        case DeviceType::DPAD:
+            for (const auto& [_, keyCode] : DPAD_KEY_CODE_MAPPING) {
+                ioctl(fd, UI_SET_KEYBIT, keyCode);
+            }
+            break;
         case DeviceType::KEYBOARD:
-            for (const auto& [ignored, keyCode] : KEY_CODE_MAPPING) {
+            for (const auto& [_, keyCode] : KEY_CODE_MAPPING) {
                 ioctl(fd, UI_SET_KEYBIT, keyCode);
             }
             break;
@@ -327,6 +340,12 @@
                       screenHeight, screenWidth);
 }
 
+static int nativeOpenUinputDpad(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                jint productId, jstring phys) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::DPAD,
+                         /* screenHeight */ 0, /* screenWidth */ 0);
+}
+
 static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
                                     jint productId, jstring phys) {
     return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
@@ -355,10 +374,10 @@
     return TEMP_FAILURE_RETRY(write(fd, &ev, sizeof(struct input_event))) == sizeof(ev);
 }
 
-static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
-                                jint action) {
-    auto keyCodeIterator = KEY_CODE_MAPPING.find(androidKeyCode);
-    if (keyCodeIterator == KEY_CODE_MAPPING.end()) {
+static bool writeKeyEvent(jint fd, jint androidKeyCode, jint action,
+                          const std::map<int, int>& keyCodeMapping) {
+    auto keyCodeIterator = keyCodeMapping.find(androidKeyCode);
+    if (keyCodeIterator == keyCodeMapping.end()) {
         ALOGE("No supportive native keycode for androidKeyCode %d", androidKeyCode);
         return false;
     }
@@ -376,6 +395,16 @@
     return true;
 }
 
+static bool nativeWriteDpadKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
+                                    jint action) {
+    return writeKeyEvent(fd, androidKeyCode, action, DPAD_KEY_CODE_MAPPING);
+}
+
+static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
+                                jint action) {
+    return writeKeyEvent(fd, androidKeyCode, action, KEY_CODE_MAPPING);
+}
+
 static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint buttonCode,
                                    jint action) {
     auto buttonCodeIterator = BUTTON_CODE_MAPPING.find(buttonCode);
@@ -461,6 +490,8 @@
 }
 
 static JNINativeMethod methods[] = {
+        {"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)I",
+         (void*)nativeOpenUinputDpad},
         {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
          (void*)nativeOpenUinputKeyboard},
         {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
@@ -468,6 +499,7 @@
         {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
          (void*)nativeOpenUinputTouchscreen},
         {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
+        {"nativeWriteDpadKeyEvent", "(III)Z", (void*)nativeWriteDpadKeyEvent},
         {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
         {"nativeWriteButtonEvent", "(III)Z", (void*)nativeWriteButtonEvent},
         {"nativeWriteTouchEvent", "(IIIIFFFF)Z", (void*)nativeWriteTouchEvent},
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index cc5ed92..51bd5b0f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -57,6 +57,8 @@
 
         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
                 anyString(), anyInt(), anyInt(), anyString());
+        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputDpad(
+                anyString(), anyInt(), anyInt(), anyString());
         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
                 anyString(), anyInt(), anyInt(), anyString());
         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
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 cc2cdba..ef203d0 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
@@ -387,6 +387,14 @@
     }
 
     @Test
+    public void createVirtualDpad_noDisplay_failsSecurityException() {
+        assertThrows(
+                SecurityException.class,
+                () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+                        PRODUCT_ID, BINDER));
+    }
+
+    @Test
     public void createVirtualKeyboard_noDisplay_failsSecurityException() {
         assertThrows(
                 SecurityException.class,
@@ -418,6 +426,17 @@
     }
 
     @Test
+    public void createVirtualDpad_noPermission_failsSecurityException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(
+                SecurityException.class,
+                () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+                        PRODUCT_ID, BINDER));
+    }
+
+    @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -468,6 +487,17 @@
     }
 
     @Test
+    public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
+                BINDER);
+        assertWithMessage("Virtual dpad should register fd when the display matches")
+          	.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+        verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString());
+    }
+
+    @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
@@ -483,7 +513,7 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
                 BINDER);
-        assertWithMessage("Virtual keyboard should register fd when the display matches")
+        assertWithMessage("Virtual mouse should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
         verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
                 anyString());
@@ -494,7 +524,7 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
                 BINDER, new Point(WIDTH, HEIGHT));
-        assertWithMessage("Virtual keyboard should register fd when the display matches")
+        assertWithMessage("Virtual touchscreen should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
         verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
                 eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
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 6e422fa..d118661 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -20,16 +20,20 @@
 import android.content.ContextWrapper
 import android.hardware.BatteryState.STATUS_CHARGING
 import android.hardware.BatteryState.STATUS_FULL
+import android.hardware.BatteryState.STATUS_UNKNOWN
 import android.hardware.input.IInputDeviceBatteryListener
+import android.hardware.input.IInputDevicesChangedListener
 import android.hardware.input.IInputManager
-import android.hardware.input.InputDeviceCountryCode
 import android.hardware.input.InputManager
 import android.os.Binder
 import android.os.IBinder
+import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
 import androidx.test.InstrumentationRegistry
+import com.android.server.input.BatteryController.UEventManager
 import org.junit.After
+import org.junit.Assert.assertEquals
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Rule
@@ -39,15 +43,27 @@
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 
+private fun createInputDevice(deviceId: Int, hasBattery: Boolean = true): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setExternal(true)
+        .setHasBattery(hasBattery)
+        .setGeneration(0)
+        .build()
+
 /**
  * Tests for {@link InputDeviceBatteryController}.
  *
@@ -60,6 +76,7 @@
         const val PID = 42
         const val DEVICE_ID = 13
         const val SECOND_DEVICE_ID = 11
+        const val TIMESTAMP = 123456789L
     }
 
     @get:Rule
@@ -69,13 +86,19 @@
     private lateinit var native: NativeInputManagerService
     @Mock
     private lateinit var iInputManager: IInputManager
+    @Mock
+    private lateinit var uEventManager: UEventManager
 
     private lateinit var batteryController: BatteryController
     private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+    private lateinit var devicesChangedListener: IInputDevicesChangedListener
+    private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
 
     @Before
     fun setup() {
         context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+        testLooper = TestLooper()
         val inputManager = InputManager.resetInstance(iInputManager)
         `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
         `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID))
@@ -83,17 +106,18 @@
         `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID))
             .thenReturn(createInputDevice(SECOND_DEVICE_ID))
 
-        batteryController = BatteryController(context, native)
+        batteryController = BatteryController(context, native, testLooper.looper, uEventManager)
+        batteryController.systemRunning()
+        val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
+        verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
+        devicesChangedListener = listenerCaptor.value
     }
 
-    private fun createInputDevice(deviceId: Int): InputDevice =
-        InputDevice.Builder()
-            .setId(deviceId)
-            .setName("Device $deviceId")
-            .setDescriptor("descriptor $deviceId")
-            .setExternal(true)
-            .setHasBattery(true)
-            .build()
+    private fun notifyDeviceChanged(deviceId: Int) {
+        deviceGenerationMap[deviceId] = deviceGenerationMap[deviceId]?.plus(1) ?: 1
+        val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
+        devicesChangedListener.onInputDevicesChanged(list.toIntArray())
+    }
 
     @After
     fun tearDown() {
@@ -169,4 +193,68 @@
         verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/),
             eq(STATUS_CHARGING), eq(0.78f), anyLong())
     }
+
+    @Test
+    fun testListenersNotifiedOnUEventNotification() {
+        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1")
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
+            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+
+        // If the battery state has changed when an UEvent is sent, the listeners are notified.
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
+        uEventListener.value!!.onUEvent(TIMESTAMP)
+        verify(listener).onBatteryStateChanged(DEVICE_ID, true /*isPresent*/, STATUS_CHARGING,
+            0.80f, TIMESTAMP)
+
+        // If the battery state has not changed when an UEvent is sent, the listeners are not
+        // notified.
+        clearInvocations(listener)
+        uEventListener.value!!.onUEvent(TIMESTAMP + 1)
+        verifyNoMoreInteractions(listener)
+
+        batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
+        verify(uEventManager).removeListener(uEventListener.capture())
+        assertEquals("The same observer must be registered and unregistered",
+            uEventListener.allValues[0], uEventListener.allValues[1])
+    }
+
+    @Test
+    fun testBatteryPresenceChanged() {
+        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1")
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
+            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+
+        // If the battery presence for the InputDevice changes, the listener is notified.
+        `when`(iInputManager.getInputDevice(DEVICE_ID))
+            .thenReturn(createInputDevice(DEVICE_ID, hasBattery = false))
+        notifyDeviceChanged(DEVICE_ID)
+        testLooper.dispatchNext()
+        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(false /*isPresent*/),
+            eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong())
+        // Since the battery is no longer present, the UEventListener should be removed.
+        verify(uEventManager).removeListener(uEventListener.value)
+
+        // If the battery becomes present again, the listener is notified.
+        `when`(iInputManager.getInputDevice(DEVICE_ID))
+            .thenReturn(createInputDevice(DEVICE_ID, hasBattery = true))
+        notifyDeviceChanged(DEVICE_ID)
+        testLooper.dispatchNext()
+        verify(listener, times(2)).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
+            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+        // Ensure that a new UEventListener was added.
+        verify(uEventManager, times(2))
+            .addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 49879efe..7986043 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1704,8 +1704,8 @@
     }
 
     @Test
-    public void testInfoIsPermittedForProfile_notAllowed() {
-        when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(false);
+    public void testInfoIsPermittedForProfile_notProfile() {
+        when(mUserProfiles.isProfileUser(anyInt())).thenReturn(false);
 
         IInterface service = mock(IInterface.class);
         when(service.asBinder()).thenReturn(mock(IBinder.class));
@@ -1714,12 +1714,12 @@
         services.registerSystemService(service, null, 10, 1000);
         ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service);
 
-        assertFalse(info.isPermittedForProfile(0));
+        assertTrue(info.isPermittedForProfile(0));
     }
 
     @Test
-    public void testInfoIsPermittedForProfile_allows() {
-        when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(true);
+    public void testInfoIsPermittedForProfile_profileAndDpmAllows() {
+        when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true);
         when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true);
 
         IInterface service = mock(IInterface.class);
@@ -1734,6 +1734,22 @@
     }
 
     @Test
+    public void testInfoIsPermittedForProfile_profileAndDpmDenies() {
+        when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true);
+        when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(false);
+
+        IInterface service = mock(IInterface.class);
+        when(service.asBinder()).thenReturn(mock(IBinder.class));
+        ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                mIpm, APPROVAL_BY_PACKAGE);
+        services.registerSystemService(service, null, 10, 1000);
+        ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service);
+        info.component = new ComponentName("a","b");
+
+        assertFalse(info.isPermittedForProfile(0));
+    }
+
+    @Test
     public void testUserProfiles_canProfileUseBoundServices_managedProfile() {
         List<UserInfo> users = new ArrayList<>();
         UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0);
@@ -1750,9 +1766,9 @@
         ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles();
         profiles.updateCache(mContext);
 
-        assertTrue(profiles.canProfileUseBoundServices(ActivityManager.getCurrentUser()));
-        assertFalse(profiles.canProfileUseBoundServices(12));
-        assertFalse(profiles.canProfileUseBoundServices(13));
+        assertFalse(profiles.isProfileUser(ActivityManager.getCurrentUser()));
+        assertTrue(profiles.isProfileUser(12));
+        assertTrue(profiles.isProfileUser(13));
     }
 
     private void resetComponentsAndPackages() {
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 2918365..107bbe1 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -79,7 +79,10 @@
         <activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true" />
-        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
+        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"
+                  android:theme="@style/WhiteBackgroundTheme"
+                  android:turnScreenOn="true"
+                  android:showWhenLocked="true"/>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
diff --git a/services/tests/wmtests/res/values/styles.xml b/services/tests/wmtests/res/values/styles.xml
new file mode 100644
index 0000000..6857ff99
--- /dev/null
+++ b/services/tests/wmtests/res/values/styles.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+    </style>
+</resources>
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 0b58428..8cd8e9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.statusBars;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertNotNull;
@@ -23,20 +27,31 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
-import android.view.WindowManager;
+import android.view.cts.surfacevalidator.BitmapPixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+import android.view.cts.surfacevalidator.SaveBitmapHelper;
 import android.window.ScreenCapture;
+import android.window.ScreenCapture.SyncScreenCaptureListener;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -45,6 +60,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -60,6 +76,8 @@
     private static final int BUFFER_HEIGHT = 100;
 
     private final Instrumentation mInstrumentation = getInstrumentation();
+    @Rule
+    public TestName mTestName = new TestName();
 
     @Rule
     public ActivityTestRule<ScreenshotActivity> mActivityRule =
@@ -95,8 +113,8 @@
         buffer.unlockCanvasAndPost(canvas);
 
         t.show(secureSC)
-                .setBuffer(secureSC, buffer)
-                .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
+                .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
+                .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
                 .apply(true);
 
         ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
@@ -112,15 +130,69 @@
         Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
         screenshot.recycle();
 
-        int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
-                new PixelColor(PixelColor.RED));
-        long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
+        BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+        Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
+        int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+        int sizeOfBitmap = bounds.width() * bounds.height();
         boolean success = numMatchingPixels == sizeOfBitmap;
         swBitmap.recycle();
 
         assertTrue(success);
     }
 
+    @Test
+    public void testCaptureDisplay() throws RemoteException {
+        IWindowManager windowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+        SurfaceControl sc = new SurfaceControl.Builder()
+                .setName("Layer")
+                .setCallsite("testCaptureDisplay")
+                .build();
+
+        SurfaceControl.Transaction t = mActivity.addChildSc(sc);
+        mInstrumentation.waitForIdleSync();
+
+        GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
+                PixelFormat.RGBA_8888,
+                GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+                        | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+
+        Canvas canvas = buffer.lockCanvas();
+        canvas.drawColor(Color.RED);
+        buffer.unlockCanvasAndPost(canvas);
+
+        Point point = mActivity.getPositionBelowStatusBar();
+        t.show(sc)
+                .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
+                .setDataSpace(sc, DataSpace.DATASPACE_SRGB)
+                .setPosition(sc, point.x, point.y)
+                .apply(true);
+
+        SyncScreenCaptureListener listener = new SyncScreenCaptureListener();
+        windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener());
+        ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot();
+        assertNotNull(hardwareBuffer);
+
+        Bitmap screenshot = hardwareBuffer.asBitmap();
+        assertNotNull(screenshot);
+
+        Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+        screenshot.recycle();
+
+        BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+        Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
+        int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+        int pixelMatchSize = bounds.width() * bounds.height();
+        boolean success = numMatchingPixels == pixelMatchSize;
+
+        if (!success) {
+            SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
+        }
+        swBitmap.recycle();
+        assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
+                success);
+    }
+
     public static class ScreenshotActivity extends Activity {
         private static final long WAIT_TIMEOUT_S = 5;
         private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -130,7 +202,6 @@
             super.onCreate(savedInstanceState);
             getWindow().getDecorView().setPointerIcon(
                     PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
 
         SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
@@ -148,88 +219,14 @@
             }
             return t;
         }
-    }
 
-    public abstract static class PixelChecker {
-        static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
-            int numMatchingPixels = 0;
-            for (int x = 0; x < bitmap.getWidth(); x++) {
-                for (int y = 0; y < bitmap.getHeight(); y++) {
-                    int color = bitmap.getPixel(x, y);
-                    if (matchesColor(pixelColor, color)) {
-                        numMatchingPixels++;
-                    }
-                }
-            }
-            return numMatchingPixels;
-        }
+        public Point getPositionBelowStatusBar() {
+            Insets statusBarInsets = getWindow()
+                    .getDecorView()
+                    .getRootWindowInsets()
+                    .getInsets(statusBars() | displayCutout());
 
-        static boolean matchesColor(PixelColor expectedColor, int color) {
-            final float red = Color.red(color);
-            final float green = Color.green(color);
-            final float blue = Color.blue(color);
-            final float alpha = Color.alpha(color);
-
-            return alpha <= expectedColor.mMaxAlpha
-                    && alpha >= expectedColor.mMinAlpha
-                    && red <= expectedColor.mMaxRed
-                    && red >= expectedColor.mMinRed
-                    && green <= expectedColor.mMaxGreen
-                    && green >= expectedColor.mMinGreen
-                    && blue <= expectedColor.mMaxBlue
-                    && blue >= expectedColor.mMinBlue;
-        }
-    }
-
-    public static class PixelColor {
-        public static final int BLACK = 0xFF000000;
-        public static final int RED = 0xFF0000FF;
-        public static final int GREEN = 0xFF00FF00;
-        public static final int BLUE = 0xFFFF0000;
-        public static final int YELLOW = 0xFF00FFFF;
-        public static final int MAGENTA = 0xFFFF00FF;
-        public static final int WHITE = 0xFFFFFFFF;
-
-        public static final int TRANSPARENT_RED = 0x7F0000FF;
-        public static final int TRANSPARENT_BLUE = 0x7FFF0000;
-        public static final int TRANSPARENT = 0x00000000;
-
-        // Default to black
-        public short mMinAlpha;
-        public short mMaxAlpha;
-        public short mMinRed;
-        public short mMaxRed;
-        public short mMinBlue;
-        public short mMaxBlue;
-        public short mMinGreen;
-        public short mMaxGreen;
-
-        public PixelColor(int color) {
-            short alpha = (short) ((color >> 24) & 0xFF);
-            short blue = (short) ((color >> 16) & 0xFF);
-            short green = (short) ((color >> 8) & 0xFF);
-            short red = (short) (color & 0xFF);
-
-            mMinAlpha = (short) getMinValue(alpha);
-            mMaxAlpha = (short) getMaxValue(alpha);
-            mMinRed = (short) getMinValue(red);
-            mMaxRed = (short) getMaxValue(red);
-            mMinBlue = (short) getMinValue(blue);
-            mMaxBlue = (short) getMaxValue(blue);
-            mMinGreen = (short) getMinValue(green);
-            mMaxGreen = (short) getMaxValue(green);
-        }
-
-        public PixelColor() {
-            this(BLACK);
-        }
-
-        private int getMinValue(short color) {
-            return Math.max(color - 4, 0);
-        }
-
-        private int getMaxValue(short color) {
-            return Math.min(color + 4, 0xFF);
+            return new Point(statusBarInsets.left, statusBarInsets.top);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 9f7f341..54b33e97 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -210,10 +210,8 @@
         assertFitted();
 
         // After the orientation of activity is changed, the display is rotated, the aspect
-        // ratio should be the same (bounds=[100, 0 - 800, 583], appBounds=[100, 0 - 800, 583]).
+        // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]).
         assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */);
-        // The notch is no longer on top.
-        assertEquals(appBounds, mActivity.getBounds());
         // Activity max bounds are sandboxed.
         assertActivityMaxBoundsSandboxed();
 
@@ -467,8 +465,6 @@
         assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation());
 
         assertEquals(origBounds.width(), currentBounds.width());
-        // The notch is on horizontal side, so current height changes from 1460 to 1400.
-        assertEquals(origBounds.height() - notchHeight, currentBounds.height());
         // Make sure the app size is the same
         assertEquals(origAppBounds.width(), appBounds.width());
         assertEquals(origAppBounds.height(), appBounds.height());
@@ -2271,7 +2267,7 @@
         prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
 
         // Bounds are letterboxed to respect the provided max aspect ratio.
-        assertEquals(mActivity.getBounds(), new Rect(0, 850, 1000, 1950));
+        assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 1100));
 
         // Move activity to split screen which has landscape size.
         mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents */ false, "test");
@@ -2338,6 +2334,8 @@
 
     @Test
     public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() {
+        // Align to center so that we don't overlap with the status bar
+        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
                 .setNotch(100)
                 .build();
@@ -2354,7 +2352,7 @@
         mActivity.mRootWindowContainer.performSurfacePlacement();
 
         Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
-        assertEquals(mBounds, new Rect(0, 750, 1000, 1950));
+        assertEquals(mBounds, new Rect(0, 900, 1000, 2000));
 
         DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
         LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
@@ -2454,7 +2452,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100),
+                /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(0, 0, 350, 700));
     }
@@ -2467,7 +2465,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
     }
@@ -2482,7 +2480,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
 
@@ -2492,7 +2490,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
     }
@@ -2505,7 +2503,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100),
+                /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
     }
@@ -2534,6 +2532,64 @@
     }
 
     @Test
+    public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
+        // The display's app bounds will be (0, 100, 1000, 2350)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500)
+                .setCanRotate(false)
+                .setCutout(0, 100, 0, 150)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity height is 2100 and the display's app bounds height is 2250, so the activity
+        // can be aligned inside parentAppBounds
+        assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200));
+    }
+    @Test
+    public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
+        // The display's app bounds will be (0, 100, 1000, 2150)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300)
+                .setCanRotate(false)
+                .setCutout(0, 100, 0, 150)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity height is 2100 and the display's app bounds height is 2050, so the activity
+        // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
+        assertEquals(mActivity.getBounds(), display.getBounds());
+    }
+
+    @Test
+    public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() {
+        // The display's app bounds will be (100, 0, 2350, 1000)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000)
+                .setCanRotate(false)
+                .setCutout(100, 0, 150, 0)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity width is 2100 and the display's app bounds width is 2250, so the activity
+        // can be aligned inside parentAppBounds
+        assertEquals(mActivity.getBounds(), new Rect(175, 0, 2275, 1000));
+    }
+    @Test
+    public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() {
+        // The display's app bounds will be (100, 0, 2150, 1000)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000)
+                .setCanRotate(false)
+                .setCutout(100, 0, 150, 0)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity width is 2100 and the display's app bounds width is 2050, so the activity
+        // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
+        assertEquals(mActivity.getBounds(), display.getBounds());
+    }
+
+    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
         // When activity width equals parent width, multiplier shouldn't have any effect.
         assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -2608,6 +2664,25 @@
                 /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400));
     }
 
+    @Test
+    public void testUpdateResolvedBoundsPosition_alignToTop() {
+        final int notchHeight = 100;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
+                .setNotch(notchHeight)
+                .build();
+        setUpApp(display);
+
+        // Prepare unresizable activity with max aspect ratio
+        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+        Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
+        // The insets should be cut for aspect ratio and then added back because the appBounds
+        // are aligned to the top of the parentAppBounds
+        assertEquals(mBounds, new Rect(0, 0, 1000, 1200));
+        assertEquals(appBounds, new Rect(0, notchHeight, 1000, 1200));
+    }
+
     private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
             float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox,
             Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
@@ -2955,7 +3030,7 @@
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
         assertTrue(mActivity.inSizeCompatMode());
         // Activity is in size compat mode but not scaled.
-        assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds());
+        assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
     }
 
     private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index aa3ca18..bf1d1fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -143,11 +143,24 @@
             mInfo.ownerUid = ownerUid;
             return this;
         }
-        Builder setNotch(int height) {
+        Builder setCutout(int left, int top, int right, int bottom) {
+            final int cutoutFillerSize = 80;
+            Rect boundLeft = left != 0 ? new Rect(0, 0, left, cutoutFillerSize) : null;
+            Rect boundTop = top != 0 ? new Rect(0, 0, cutoutFillerSize, top) : null;
+            Rect boundRight = right != 0 ? new Rect(mInfo.logicalWidth - right, 0,
+                    mInfo.logicalWidth, cutoutFillerSize) : null;
+            Rect boundBottom = bottom != 0
+                    ? new Rect(0, mInfo.logicalHeight - bottom, cutoutFillerSize,
+                    mInfo.logicalHeight) : null;
+
             mInfo.displayCutout = new DisplayCutout(
-                    Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
+                    Insets.of(left, top, right, bottom),
+                    boundLeft, boundTop, boundRight, boundBottom);
             return this;
         }
+        Builder setNotch(int height) {
+            return setCutout(0, height, 0, 0);
+        }
         Builder setStatusBarHeight(int height) {
             mStatusBarHeight = height;
             return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 64e15f2..e2dff96 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -732,7 +732,7 @@
         assertTrue(ime.mToken.inTransition());
         assertTrue(task.inTransition());
         assertTrue(asyncRotationController.isTargetToken(decorToken));
-        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar));
+        assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
 
         screenDecor.setOrientationChanging(false);
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
@@ -747,6 +747,7 @@
         // The transaction is committed, so fade-in animation for status bar is consumed.
         transactionCommittedListener.onTransactionCommitted();
         assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
+        assertShouldFreezeInsetsPosition(asyncRotationController, navBar, false);
 
         // Navigation bar finishes drawing after the start transaction, so its fade-in animation
         // can execute directly.
@@ -782,7 +783,7 @@
         final AsyncRotationController asyncRotationController =
                 mDisplayContent.getAsyncRotationController();
         assertNotNull(asyncRotationController);
-        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
+        assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
 
         statusBar.setOrientationChanging(true);
         player.startTransition();
@@ -828,7 +829,7 @@
         final AsyncRotationController asyncRotationController =
                 mDisplayContent.getAsyncRotationController();
         assertNotNull(asyncRotationController);
-        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
+        assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
         assertTrue(app.getTask().inTransition());
 
         player.start();
@@ -863,6 +864,15 @@
         assertNull(mDisplayContent.getAsyncRotationController());
     }
 
+    private static void assertShouldFreezeInsetsPosition(AsyncRotationController controller,
+            WindowState w, boolean freeze) {
+        if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+            // Non blast sync should never freeze insets position.
+            freeze = false;
+        }
+        assertEquals(freeze, controller.shouldFreezeInsetsPosition(w));
+    }
+
     @Test
     public void testDeferRotationForTransientLaunch() {
         final TestTransitionPlayer player = registerTestTransitionPlayer();
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 4293d48..9c9f5db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -242,7 +242,7 @@
         // Ensure letterbox vertical position multiplier is not overridden on any device target.
         // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
         // may be set on some device form factors.
-        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.0f);
         // Ensure letterbox horizontal reachability treatment isn't overridden on any device target.
         // {@link com.android.internal.R.bool.config_letterboxIsHorizontalReachabilityEnabled},
         // may be set on some device form factors.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 4b9b4a9..4ee066c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1284,12 +1284,13 @@
             }
         }
 
-        @android.annotation.EnforcePermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD})
         @Override
         public void startListeningFromMic(
                 AudioFormat audioFormat,
                 IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
                 throws RemoteException {
+            enforceCallingPermission(Manifest.permission.RECORD_AUDIO);
+            enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
@@ -1349,12 +1350,13 @@
             }
         }
 
-        @android.annotation.EnforcePermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD})
         @Override
         public void triggerHardwareRecognitionEventForTest(
                 SoundTrigger.KeyphraseRecognitionEvent event,
                 IHotwordRecognitionStatusCallback callback)
                 throws RemoteException {
+            enforceCallingPermission(Manifest.permission.RECORD_AUDIO);
+            enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index e7d95e4..58974ee 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.ParcelUuid;
+import android.provider.DeviceConfig;
 
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
@@ -57,6 +58,9 @@
 public final class AnomalyReporter {
     private static final String TAG = "AnomalyReporter";
 
+    private static final String KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED =
+            "is_telephony_anomaly_report_enabled";
+
     private static Context sContext = null;
 
     private static Map<UUID, Integer> sEvents = new ConcurrentHashMap<>();
@@ -101,6 +105,12 @@
      * @param carrierId the carrier of the id associated with this event.
      */
     public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) {
+        // Don't report if the server-side flag isn't loaded, as it implies other anomaly report
+        // related config hasn't loaded.
+        boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED, false);
+        if (!isAnomalyReportEnabledFromServer) return;
+
         if (sContext == null) {
             Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId);
             return;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 16f8fd9..b6944eb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -489,7 +489,10 @@
     /**
      * Control whether users receive a simplified network settings UI and improved network
      * selection.
+     *
+     * @deprecated Never implemented. Has no behavior impact when override. DO NOT USE.
      */
+    @Deprecated
     public static final String
             KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
 
@@ -8666,7 +8669,7 @@
      *     or EIMS-->
      *     <item value="source=EUTRAN, target=IWLAN, type=disallowed, capabilities=IMS|EIMS"/>
      *     <!-- Handover is always allowed in any condition. -->
-     *     <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN,
+     *     <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN,
      *         target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"/>
      * </string-array>
      *
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index cb985bf..0109ae6 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -18,6 +18,7 @@
 
 import static android.text.TextUtils.formatSimple;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +38,9 @@
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
+import android.telephony.SubscriptionManager.ProfileClass;
+import android.telephony.SubscriptionManager.SimDisplayNameSource;
+import android.telephony.SubscriptionManager.SubscriptionType;
 import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -55,7 +59,6 @@
  * A Parcelable class for Subscription Information.
  */
 public class SubscriptionInfo implements Parcelable {
-
     /**
      * Size of text to render on the icon.
      */
@@ -65,162 +68,180 @@
      * Subscription Identifier, this is a device unique number
      * and not an index into an array
      */
-    private int mId;
+    private final int mId;
 
     /**
-     * The GID for a SIM that maybe associated with this subscription, empty if unknown
+     * The ICCID of the SIM that is associated with this subscription, empty if unknown.
      */
-    private String mIccId;
+    @NonNull
+    private final String mIccId;
 
     /**
-     * The index of the slot that currently contains the subscription
-     * and not necessarily unique and maybe INVALID_SLOT_ID if unknown
+     * The index of the SIM slot that currently contains the subscription and not necessarily unique
+     * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription
+     * is inactive.
      */
-    private int mSimSlotIndex;
+    private final int mSimSlotIndex;
 
     /**
-     * The name displayed to the user that identifies this subscription
+     * The name displayed to the user that identifies this subscription. This name is used
+     * in Settings page and can be renamed by the user.
      */
-    private CharSequence mDisplayName;
+    @NonNull
+    private final CharSequence mDisplayName;
 
     /**
-     * String that identifies SPN/PLMN
-     * TODO : Add a new field that identifies only SPN for a sim
+     * The name displayed to the user that identifies subscription provider name. This name is the
+     * SPN displayed in status bar and many other places. Can't be renamed by the user.
      */
-    private CharSequence mCarrierName;
+    @NonNull
+    private final CharSequence mCarrierName;
 
     /**
      * The subscription carrier id.
+     *
      * @see TelephonyManager#getSimCarrierId()
      */
-    private int mCarrierId;
+    private final int mCarrierId;
 
     /**
-     * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN,
-     * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT.
+     * The source of the {@link #mCarrierName}.
      */
-    private int mNameSource;
+    @SimDisplayNameSource
+    private final int mNameSource;
 
     /**
-     * The color to be used for tinting the icon when displaying to the user
+     * The color to be used for tinting the icon when displaying to the user.
      */
-    private int mIconTint;
+    private final int mIconTint;
 
     /**
-     * A number presented to the user identify this subscription
+     * The number presented to the user identify this subscription.
      */
-    private String mNumber;
+    @NonNull
+    private final String mNumber;
 
     /**
-     * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE
+     * Whether user enables data roaming for this subscription or not. Either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
-    private int mDataRoaming;
+    private final int mDataRoaming;
 
     /**
-     * SIM icon bitmap cache
-     */
-    @Nullable private Bitmap mIconBitmap;
-
-    /**
-     * Mobile Country Code
-     */
-    private String mMcc;
-
-    /**
-     * Mobile Network Code
-     */
-    private String mMnc;
-
-    /**
-     * EHPLMNs associated with the subscription
-     */
-    private String[] mEhplmns;
-
-    /**
-     * HPLMNs associated with the subscription
-     */
-    private String[] mHplmns;
-
-    /**
-     * ISO Country code for the subscription's provider
-     */
-    private String mCountryIso;
-
-    /**
-     * Whether the subscription is an embedded one.
-     */
-    private boolean mIsEmbedded;
-
-    /**
-     * The access rules for this subscription, if it is embedded and defines any.
-     * This does not include access rules for non-embedded subscriptions.
+     * SIM icon bitmap cache.
      */
     @Nullable
-    private UiccAccessRule[] mNativeAccessRules;
+    private Bitmap mIconBitmap;
+
+    /**
+     * Mobile Country Code.
+     */
+    @Nullable
+    private final String mMcc;
+
+    /**
+     * Mobile Network Code.
+     */
+    @Nullable
+    private final String mMnc;
+
+    /**
+     * EHPLMNs associated with the subscription.
+     */
+    @NonNull
+    private final String[] mEhplmns;
+
+    /**
+     * HPLMNs associated with the subscription.
+     */
+    @NonNull
+    private final String[] mHplmns;
+
+    /**
+     * ISO Country code for the subscription's provider.
+     */
+    @NonNull
+    private final String mCountryIso;
+
+    /**
+     * Whether the subscription is from eSIM.
+     */
+    private final boolean mIsEmbedded;
+
+    /**
+     * The access rules for this subscription, if it is embedded and defines any. This does not
+     * include access rules for non-embedded subscriptions.
+     */
+    @Nullable
+    private final UiccAccessRule[] mNativeAccessRules;
 
     /**
      * The carrier certificates for this subscription that are saved in carrier configs.
      * This does not include access rules from the Uicc, whether embedded or non-embedded.
      */
     @Nullable
-    private UiccAccessRule[] mCarrierConfigAccessRules;
+    private final UiccAccessRule[] mCarrierConfigAccessRules;
 
     /**
      * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the
      * EID for an eUICC card.
      */
-    private String mCardString;
+    @NonNull
+    private final String mCardString;
 
     /**
-     * The card ID of the SIM card. This maps uniquely to the card string.
+     * The card ID of the SIM card. This maps uniquely to {@link #mCardString}.
      */
-    private int mCardId;
+    private final int mCardId;
 
     /**
      * Whether the subscription is opportunistic.
      */
-    private boolean mIsOpportunistic;
+    private final boolean mIsOpportunistic;
 
     /**
-     * A UUID assigned to the subscription group. It returns null if not assigned.
-     * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details.
+     * A UUID assigned to the subscription group. {@code null} if not assigned.
+     *
+     * @see SubscriptionManager#createSubscriptionGroup(List)
      */
     @Nullable
-    private ParcelUuid mGroupUUID;
+    private final ParcelUuid mGroupUuid;
 
     /**
-     * A package name that specifies who created the group. Null if mGroupUUID is null.
+     * A package name that specifies who created the group. Empty if not available.
      */
-    private String mGroupOwner;
+    @NonNull
+    private final String mGroupOwner;
 
     /**
-     * Whether group of the subscription is disabled.
-     * This is only useful if it's a grouped opportunistic subscription. In this case, if all
-     * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM
-     * or deactivated eSIM profile), we should disable this opportunistic subscription.
+     * Whether group of the subscription is disabled. This is only useful if it's a grouped
+     * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions
+     * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable
+     * this opportunistic subscription.
      */
-    private boolean mIsGroupDisabled = false;
+    private final boolean mIsGroupDisabled;
 
     /**
-     * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
-     * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
-     * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
-     * The profile class will be populated from the profile metadata if present. Otherwise,
-     * the profile class defaults to unset if there is no profile metadata or the subscription
-     * is not on an eUICC ({@link #isEmbedded} returns false).
+     * The profile class populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+     * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+     * {@code false}).
      */
-    private int mProfileClass;
+    @ProfileClass
+    private final int mProfileClass;
 
     /**
-     * Type of subscription
+     * Type of the subscription.
      */
-    private int mSubscriptionType;
+    @SubscriptionType
+    private final int mType;
 
     /**
      * Whether uicc applications are configured to enable or disable.
      * By default it's true.
      */
-    private boolean mAreUiccApplicationsEnabled = true;
+    private final boolean mAreUiccApplicationsEnabled;
 
     /**
      * The port index of the Uicc card.
@@ -230,25 +251,16 @@
     /**
      * Subscription's preferred usage setting.
      */
-    private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
-
-    /**
-     * Public copy constructor.
-     * @hide
-     */
-    public SubscriptionInfo(SubscriptionInfo info) {
-        this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName,
-                info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap,
-                info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules,
-                info.mCardString, info.mCardId, info.mIsOpportunistic,
-                info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled,
-                info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner,
-                info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled);
-    }
+    @UsageSetting
+    private final int mUsageSetting;
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -262,7 +274,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -276,7 +292,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -293,7 +313,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -311,49 +335,94 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
-            boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+            boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled,
             int carrierId, int profileClass, int subType, @Nullable String groupOwner,
             @Nullable UiccAccessRule[] carrierConfigAccessRules,
             boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
-        this.mDisplayName = displayName;
+        this.mDisplayName =  displayName;
         this.mCarrierName = carrierName;
         this.mNameSource = nameSource;
         this.mIconTint = iconTint;
         this.mNumber = number;
         this.mDataRoaming = roaming;
         this.mIconBitmap = icon;
-        this.mMcc = mcc;
-        this.mMnc = mnc;
-        this.mCountryIso = countryIso;
+        this.mMcc = TextUtils.emptyIfNull(mcc);
+        this.mMnc = TextUtils.emptyIfNull(mnc);
+        this.mHplmns = null;
+        this.mEhplmns = null;
+        this.mCountryIso = TextUtils.emptyIfNull(countryIso);
         this.mIsEmbedded = isEmbedded;
         this.mNativeAccessRules = nativeAccessRules;
-        this.mCardString = cardString;
+        this.mCardString = TextUtils.emptyIfNull(cardString);
         this.mCardId = cardId;
         this.mIsOpportunistic = isOpportunistic;
-        this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID);
+        this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
         this.mIsGroupDisabled = isGroupDisabled;
         this.mCarrierId = carrierId;
         this.mProfileClass = profileClass;
-        this.mSubscriptionType = subType;
-        this.mGroupOwner = groupOwner;
+        this.mType = subType;
+        this.mGroupOwner = TextUtils.emptyIfNull(groupOwner);
         this.mCarrierConfigAccessRules = carrierConfigAccessRules;
         this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
         this.mPortIndex = portIndex;
         this.mUsageSetting = usageSetting;
     }
+
     /**
-     * @return the subscription ID.
+     * Constructor from builder.
+     *
+     * @param builder Builder of {@link SubscriptionInfo}.
+     */
+    private SubscriptionInfo(@NonNull Builder builder) {
+        this.mId = builder.mId;
+        this.mIccId = builder.mIccId;
+        this.mSimSlotIndex = builder.mSimSlotIndex;
+        this.mDisplayName = builder.mDisplayName;
+        this.mCarrierName = builder.mCarrierName;
+        this.mNameSource = builder.mNameSource;
+        this.mIconTint = builder.mIconTint;
+        this.mNumber = builder.mNumber;
+        this.mDataRoaming = builder.mDataRoaming;
+        this.mIconBitmap = builder.mIconBitmap;
+        this.mMcc = builder.mMcc;
+        this.mMnc = builder.mMnc;
+        this.mEhplmns = builder.mEhplmns;
+        this.mHplmns = builder.mHplmns;
+        this.mCountryIso = builder.mCountryIso;
+        this.mIsEmbedded = builder.mIsEmbedded;
+        this.mNativeAccessRules = builder.mNativeAccessRules;
+        this.mCardString = builder.mCardString;
+        this.mCardId = builder.mCardId;
+        this.mIsOpportunistic = builder.mIsOpportunistic;
+        this.mGroupUuid = builder.mGroupUuid;
+        this.mIsGroupDisabled = builder.mIsGroupDisabled;
+        this.mCarrierId = builder.mCarrierId;
+        this.mProfileClass = builder.mProfileClass;
+        this.mType = builder.mType;
+        this.mGroupOwner = builder.mGroupOwner;
+        this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules;
+        this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled;
+        this.mPortIndex = builder.mPortIndex;
+        this.mUsageSetting = builder.mUsageSetting;
+    }
+
+    /**
+     * @return The subscription ID.
      */
     public int getSubscriptionId() {
-        return this.mId;
+        return mId;
     }
 
     /**
@@ -370,78 +439,56 @@
      * @return the ICC ID, or an empty string if one of these requirements is not met
      */
     public String getIccId() {
-        return this.mIccId;
+        return mIccId;
     }
 
     /**
-     * @hide
-     */
-    public void clearIccId() {
-        this.mIccId = "";
-    }
-
-    /**
-     * @return the slot index of this Subscription's SIM card.
+     * @return The index of the SIM slot that currently contains the subscription and not
+     * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or
+     * the subscription is inactive.
      */
     public int getSimSlotIndex() {
-        return this.mSimSlotIndex;
+        return mSimSlotIndex;
     }
 
     /**
-     * @return the carrier id of this Subscription carrier.
+     * @return The carrier id of this subscription carrier.
+     *
      * @see TelephonyManager#getSimCarrierId()
      */
     public int getCarrierId() {
-        return this.mCarrierId;
+        return mCarrierId;
     }
 
     /**
-     * @return the name displayed to the user that identifies this subscription
+     * @return The name displayed to the user that identifies this subscription. This name is
+     * used in Settings page and can be renamed by the user.
+     *
+     * @see #getCarrierName()
      */
     public CharSequence getDisplayName() {
-        return this.mDisplayName;
+        return mDisplayName;
     }
 
     /**
-     * Sets the name displayed to the user that identifies this subscription
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setDisplayName(CharSequence name) {
-        this.mDisplayName = name;
-    }
-
-    /**
-     * @return the name displayed to the user that identifies Subscription provider name
+     * @return The name displayed to the user that identifies subscription provider name. This name
+     * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+     *
+     * @see #getDisplayName()
      */
     public CharSequence getCarrierName() {
-        return this.mCarrierName;
+        return mCarrierName;
     }
 
     /**
-     * Sets the name displayed to the user that identifies Subscription provider name
-     * @hide
-     */
-    public void setCarrierName(CharSequence name) {
-        this.mCarrierName = name;
-    }
-
-    /**
-     * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or
-     * NAME_SOURCE_USER_INPUT.
+     * @return The source of the {@link #getCarrierName()}.
+     *
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SimDisplayNameSource
     public int getNameSource() {
-        return this.mNameSource;
-    }
-
-    /**
-     * @hide
-     */
-    public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) {
-        mEhplmns = ehplmns;
-        mHplmns = hplmns;
+        return mNameSource;
     }
 
     /**
@@ -499,15 +546,6 @@
     }
 
     /**
-     * Sets the color displayed to the user that identifies this subscription
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setIconTint(int iconTint) {
-        this.mIconTint = iconTint;
-    }
-
-    /**
      * Returns the number of this subscription.
      *
      * Starting with API level 30, returns the number of this subscription if the calling app meets
@@ -533,28 +571,23 @@
     }
 
     /**
-     * @hide
-     */
-    public void clearNumber() {
-        mNumber = "";
-    }
-
-    /**
-     * @return the data roaming state for this subscription, either
-     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+     * Whether user enables data roaming for this subscription or not. Either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
     public int getDataRoaming() {
-        return this.mDataRoaming;
+        return mDataRoaming;
     }
 
     /**
-     * @return the MCC.
+     * @return The mobile country code.
+     *
      * @deprecated Use {@link #getMccString()} instead.
      */
     @Deprecated
     public int getMcc() {
         try {
-            return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc);
+            return mMcc == null ? 0 : Integer.parseInt(mMcc);
         } catch (NumberFormatException e) {
             Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number");
             return 0;
@@ -562,13 +595,14 @@
     }
 
     /**
-     * @return the MNC.
+     * @return The mobile network code.
+     *
      * @deprecated Use {@link #getMncString()} instead.
      */
     @Deprecated
     public int getMnc() {
         try {
-            return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc);
+            return mMnc == null ? 0 : Integer.parseInt(mMnc);
         } catch (NumberFormatException e) {
             Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number");
             return 0;
@@ -576,36 +610,40 @@
     }
 
     /**
-     * @return The MCC, as a string.
+     * @return The mobile country code.
      */
-    public @Nullable String getMccString() {
-        return this.mMcc;
+    @Nullable
+    public String getMccString() {
+        return mMcc;
     }
 
     /**
-     * @return The MNC, as a string.
+     * @return The mobile network code.
      */
-    public @Nullable String getMncString() {
-        return this.mMnc;
+    @Nullable
+    public String getMncString() {
+        return mMnc;
     }
 
     /**
-     * @return the ISO country code
+     * @return The ISO country code. Empty if not available.
      */
     public String getCountryIso() {
-        return this.mCountryIso;
+        return mCountryIso;
     }
 
-    /** @return whether the subscription is an eUICC one. */
+    /**
+     * @return {@code true} if the subscription is from eSIM.
+     */
     public boolean isEmbedded() {
-        return this.mIsEmbedded;
+        return mIsEmbedded;
     }
 
     /**
      * An opportunistic subscription connects to a network that is
      * limited in functionality and / or coverage.
      *
-     * @return whether subscription is opportunistic.
+     * @return Whether subscription is opportunistic.
      */
     public boolean isOpportunistic() {
         return mIsOpportunistic;
@@ -617,23 +655,18 @@
      * Such that those subscriptions will have some affiliated behaviors such as opportunistic
      * subscription may be invisible to the user.
      *
-     * @return group UUID a String of group UUID if it belongs to a group. Otherwise
-     * it will return null.
+     * @return Group UUID a String of group UUID if it belongs to a group. Otherwise
+     * {@code null}.
      */
-    public @Nullable ParcelUuid getGroupUuid() {
-        return mGroupUUID;
+    @Nullable
+    public ParcelUuid getGroupUuid() {
+        return mGroupUuid;
     }
 
     /**
      * @hide
      */
-    public void clearGroupUuid() {
-        this.mGroupUUID = null;
-    }
-
-    /**
-     * @hide
-     */
+    @NonNull
     public List<String> getEhplmns() {
         return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns);
     }
@@ -641,36 +674,45 @@
     /**
      * @hide
      */
+    @NonNull
     public List<String> getHplmns() {
         return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns);
     }
 
     /**
-     * Return owner package of group the subscription belongs to.
+     * @return The owner package of group the subscription belongs to.
      *
      * @hide
      */
-    public @Nullable String getGroupOwner() {
+    @NonNull
+    public String getGroupOwner() {
         return mGroupOwner;
     }
 
     /**
-     * @return the profile class of this subscription.
+     * @return The profile class populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+     * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return
+     * {@code false}).
+     *
      * @hide
      */
     @SystemApi
-    public @SubscriptionManager.ProfileClass int getProfileClass() {
-        return this.mProfileClass;
+    @ProfileClass
+    public int getProfileClass() {
+        return mProfileClass;
     }
 
     /**
      * This method returns the type of a subscription. It can be
      * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
      * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
-     * @return the type of subscription
+     *
+     * @return The type of the subscription.
      */
-    public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
-        return mSubscriptionType;
+    @SubscriptionType
+    public int getSubscriptionType() {
+        return mType;
     }
 
     /**
@@ -679,7 +721,7 @@
      * returns true).
      *
      * @param context Context of the application to check.
-     * @return whether the app is authorized to manage this subscription per its metadata.
+     * @return Whether the app is authorized to manage this subscription per its metadata.
      * @hide
      * @deprecated - Do not use.
      */
@@ -700,7 +742,7 @@
      */
     @Deprecated
     public boolean canManageSubscription(Context context, String packageName) {
-        List<UiccAccessRule> allAccessRules = getAllAccessRules();
+        List<UiccAccessRule> allAccessRules = getAccessRules();
         if (allAccessRules == null) {
             return false;
         }
@@ -723,24 +765,14 @@
     }
 
     /**
-     * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who
-     * is authorized to manage this subscription.
-     * TODO and fix it properly in R / master: either deprecate this and have 3 APIs
-     *  native + carrier + all, or have this return all by default.
+     * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to
+     * manage this subscription.
+     *
      * @hide
      */
     @SystemApi
-    public @Nullable List<UiccAccessRule> getAccessRules() {
-        if (mNativeAccessRules == null) return null;
-        return Arrays.asList(mNativeAccessRules);
-    }
-
-    /**
-     * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs
-     * dictating who is authorized to manage this subscription.
-     * @hide
-     */
-    public @Nullable List<UiccAccessRule> getAllAccessRules() {
+    @Nullable
+    public List<UiccAccessRule> getAccessRules() {
         List<UiccAccessRule> merged = new ArrayList<>();
         if (mNativeAccessRules != null) {
             merged.addAll(getAccessRules());
@@ -762,50 +794,38 @@
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile
      * owner access is deprecated and will be removed in a future release.
      *
-     * @return the card string of the SIM card which contains the subscription or an empty string
+     * @return The card string of the SIM card which contains the subscription or an empty string
      * if these requirements are not met. The card string is the ICCID for UICCs or the EID for
      * eUICCs.
+     *
      * @hide
-     * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java
      */
+    @NonNull
     public String getCardString() {
-        return this.mCardString;
+        return mCardString;
     }
 
     /**
-     * @hide
-     */
-    public void clearCardString() {
-        this.mCardString = "";
-    }
-
-    /**
-     * Returns the card ID of the SIM card which contains the subscription (see
-     * {@link UiccCardInfo#getCardId()}.
-     * @return the cardId
+     * @return The card ID of the SIM card which contains the subscription.
+     *
+     * @see UiccCardInfo#getCardId().
      */
     public int getCardId() {
-        return this.mCardId;
+        return mCardId;
     }
     /**
-     * Returns the port index of the SIM card which contains the subscription.
-     *
-     * @return the portIndex
+     * @return The port index of the SIM card which contains the subscription.
      */
     public int getPortIndex() {
-        return this.mPortIndex;
+        return mPortIndex;
     }
 
     /**
-     * Set whether the subscription's group is disabled.
-     * @hide
-     */
-    public void setGroupDisabled(boolean isGroupDisabled) {
-        this.mIsGroupDisabled = isGroupDisabled;
-    }
-
-    /**
-     * Return whether the subscription's group is disabled.
+     * @return {@code true} if the group of the subscription is disabled. This is only useful if
+     * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic)
+     * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we
+     * should disable this opportunistic subscription.
+     *
      * @hide
      */
     @SystemApi
@@ -814,7 +834,7 @@
     }
 
     /**
-     * Return whether uicc applications are set to be enabled or disabled.
+     * @return {@code true} if Uicc applications are set to be enabled or disabled.
      * @hide
      */
     @SystemApi
@@ -825,56 +845,50 @@
     /**
      * Get the usage setting for this subscription.
      *
-     * @return the usage setting used for this subscription.
+     * @return The usage setting used for this subscription.
      */
-    public @UsageSetting int getUsageSetting() {
+    @UsageSetting
+    public int getUsageSetting() {
         return mUsageSetting;
     }
 
-    public static final @android.annotation.NonNull
-            Parcelable.Creator<SubscriptionInfo> CREATOR =
-                    new Parcelable.Creator<SubscriptionInfo>() {
+    @NonNull
+    public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
+            new Parcelable.Creator<SubscriptionInfo>() {
         @Override
         public SubscriptionInfo createFromParcel(Parcel source) {
-            int id = source.readInt();
-            String iccId = source.readString();
-            int simSlotIndex = source.readInt();
-            CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            int nameSource = source.readInt();
-            int iconTint = source.readInt();
-            String number = source.readString();
-            int dataRoaming = source.readInt();
-            String mcc = source.readString();
-            String mnc = source.readString();
-            String countryIso = source.readString();
-            boolean isEmbedded = source.readBoolean();
-            UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR);
-            String cardString = source.readString();
-            int cardId = source.readInt();
-            int portId = source.readInt();
-            boolean isOpportunistic = source.readBoolean();
-            String groupUUID = source.readString();
-            boolean isGroupDisabled = source.readBoolean();
-            int carrierid = source.readInt();
-            int profileClass = source.readInt();
-            int subType = source.readInt();
-            String[] ehplmns = source.createStringArray();
-            String[] hplmns = source.createStringArray();
-            String groupOwner = source.readString();
-            UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
-                UiccAccessRule.CREATOR);
-            boolean areUiccApplicationsEnabled = source.readBoolean();
-            int usageSetting = source.readInt();
-
-            SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
-                    carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
-                    mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
-                    isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
-                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
-                    portId, usageSetting);
-            info.setAssociatedPlmns(ehplmns, hplmns);
-            return info;
+            return new Builder()
+                    .setId(source.readInt())
+                    .setIccId(source.readString())
+                    .setSimSlotIndex(source.readInt())
+                    .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+                    .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+                    .setNameSource(source.readInt())
+                    .setIconTint(source.readInt())
+                    .setNumber(source.readString())
+                    .setDataRoaming(source.readInt())
+                    .setMcc(source.readString())
+                    .setMnc(source.readString())
+                    .setCountryIso(source.readString())
+                    .setEmbedded(source.readBoolean())
+                    .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR))
+                    .setCardString(source.readString())
+                    .setCardId(source.readInt())
+                    .setPortIndex(source.readInt())
+                    .setOpportunistic(source.readBoolean())
+                    .setGroupUuid(source.readString8())
+                    .setGroupDisabled(source.readBoolean())
+                    .setCarrierId(source.readInt())
+                    .setProfileClass(source.readInt())
+                    .setType(source.readInt())
+                    .setEhplmns(source.createStringArray())
+                    .setHplmns(source.createStringArray())
+                    .setGroupOwner(source.readString())
+                    .setCarrierConfigAccessRules(source.createTypedArray(
+                            UiccAccessRule.CREATOR))
+                    .setUiccApplicationsEnabled(source.readBoolean())
+                    .setUsageSetting(source.readInt())
+                    .build();
         }
 
         @Override
@@ -904,11 +918,11 @@
         dest.writeInt(mCardId);
         dest.writeInt(mPortIndex);
         dest.writeBoolean(mIsOpportunistic);
-        dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString());
+        dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString());
         dest.writeBoolean(mIsGroupDisabled);
         dest.writeInt(mCarrierId);
         dest.writeInt(mProfileClass);
-        dest.writeInt(mSubscriptionType);
+        dest.writeInt(mType);
         dest.writeStringArray(mEhplmns);
         dest.writeStringArray(mHplmns);
         dest.writeString(mGroupOwner);
@@ -923,6 +937,11 @@
     }
 
     /**
+     * Get ICCID stripped PII information on user build.
+     *
+     * @param iccId The original ICCID.
+     * @return The stripped string.
+     *
      * @hide
      */
     public static String givePrintableIccid(String iccId) {
@@ -951,12 +970,12 @@
                 + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
                 + " portIndex=" + mPortIndex
-                + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+                + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid
                 + " isGroupDisabled=" + mIsGroupDisabled
                 + " profileClass=" + mProfileClass
                 + " ehplmns=" + Arrays.toString(mEhplmns)
                 + " hplmns=" + Arrays.toString(mHplmns)
-                + " subscriptionType=" + mSubscriptionType
+                + " mType=" + mType
                 + " groupOwner=" + mGroupOwner
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
                 + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
@@ -966,7 +985,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
-                mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
+                mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
                 mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules),
                 mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner,
                 mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting);
@@ -974,16 +993,9 @@
 
     @Override
     public boolean equals(Object obj) {
-        if (obj == null) return false;
-        if (obj == this) return true;
-
-        SubscriptionInfo toCompare;
-        try {
-            toCompare = (SubscriptionInfo) obj;
-        } catch (ClassCastException ex) {
-            return false;
-        }
-
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        SubscriptionInfo toCompare = (SubscriptionInfo) obj;
         return mId == toCompare.mId
                 && mSimSlotIndex == toCompare.mSimSlotIndex
                 && mNameSource == toCompare.mNameSource
@@ -994,7 +1006,7 @@
                 && mIsGroupDisabled == toCompare.mIsGroupDisabled
                 && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled
                 && mCarrierId == toCompare.mCarrierId
-                && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
+                && Objects.equals(mGroupUuid, toCompare.mGroupUuid)
                 && Objects.equals(mIccId, toCompare.mIccId)
                 && Objects.equals(mNumber, toCompare.mNumber)
                 && Objects.equals(mMcc, toCompare.mMcc)
@@ -1012,4 +1024,629 @@
                 && Arrays.equals(mHplmns, toCompare.mHplmns)
                 && mUsageSetting == toCompare.mUsageSetting;
     }
+
+    /**
+     * The builder class of {@link SubscriptionInfo}.
+     *
+     * @hide
+     */
+    public static class Builder {
+        /**
+         * The subscription id.
+         */
+        private int mId = 0;
+
+        /**
+         * The ICCID of the SIM that is associated with this subscription, empty if unknown.
+         */
+        @NonNull
+        private String mIccId = "";
+
+        /**
+         * The index of the SIM slot that currently contains the subscription and not necessarily
+         * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the
+         * subscription is inactive.
+         */
+        private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
+        /**
+         * The name displayed to the user that identifies this subscription. This name is used
+         * in Settings page and can be renamed by the user.
+         */
+        @NonNull
+        private CharSequence mDisplayName = "";
+
+        /**
+         * The name displayed to the user that identifies subscription provider name. This name
+         * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+         */
+        @NonNull
+        private CharSequence mCarrierName = "";
+
+        /**
+         * The source of the carrier name.
+         */
+        @SimDisplayNameSource
+        private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+
+        /**
+         * The color to be used for tinting the icon when displaying to the user.
+         */
+        private int mIconTint = 0;
+
+        /**
+         * The number presented to the user identify this subscription.
+         */
+        @NonNull
+        private String mNumber = "";
+
+        /**
+         * Whether user enables data roaming for this subscription or not. Either
+         * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+         * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+         */
+        private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE;
+
+        /**
+         * SIM icon bitmap cache.
+         */
+        @Nullable
+        private Bitmap mIconBitmap = null;
+
+        /**
+         * The mobile country code.
+         */
+        @Nullable
+        private String mMcc = null;
+
+        /**
+         * The mobile network code.
+         */
+        @Nullable
+        private String mMnc = null;
+
+        /**
+         * EHPLMNs associated with the subscription.
+         */
+        @NonNull
+        private String[] mEhplmns = new String[0];
+
+        /**
+         * HPLMNs associated with the subscription.
+         */
+        @NonNull
+        private String[] mHplmns = new String[0];
+
+        /**
+         * The ISO Country code for the subscription's provider.
+         */
+        @NonNull
+        private String mCountryIso = "";
+
+        /**
+         * Whether the subscription is from eSIM.
+         */
+        private boolean mIsEmbedded = false;
+
+        /**
+         * The native access rules for this subscription, if it is embedded and defines any. This
+         * does not include access rules for non-embedded subscriptions.
+         */
+        @Nullable
+        private UiccAccessRule[] mNativeAccessRules = null;
+
+        /**
+         * The card string of the SIM card.
+         */
+        @NonNull
+        private String mCardString = "";
+
+        /**
+         * The card ID of the SIM card which contains the subscription.
+         */
+        private int mCardId = -1;
+
+        /**
+         * Whether the subscription is opportunistic or not.
+         */
+        private boolean mIsOpportunistic = false;
+
+        /**
+         * The group UUID of the subscription group.
+         */
+        @Nullable
+        private ParcelUuid mGroupUuid = null;
+
+        /**
+         * Whether group of the subscription is disabled. This is only useful if it's a grouped
+         * opportunistic subscription. In this case, if all primary (non-opportunistic)
+         * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+         * we should disable this opportunistic subscription.
+         */
+        private boolean mIsGroupDisabled = false;
+
+        /**
+         * The carrier id.
+         *
+         * @see TelephonyManager#getSimCarrierId()
+         */
+        private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
+        /**
+         * The profile class populated from the profile metadata if present. Otherwise, the profile
+         * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile
+         * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+         * {@code false}).
+         */
+        @ProfileClass
+        private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET;
+
+        /**
+         * The subscription type.
+         */
+        @SubscriptionType
+        private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM;
+
+        /**
+         * The owner package of group the subscription belongs to.
+         */
+        @NonNull
+        private String mGroupOwner = "";
+
+        /**
+         * The carrier certificates for this subscription that are saved in carrier configs.
+         * This does not include access rules from the Uicc, whether embedded or non-embedded.
+         */
+        @Nullable
+        private UiccAccessRule[] mCarrierConfigAccessRules = null;
+
+        /**
+         * Whether Uicc applications are configured to enable or not.
+         */
+        private boolean mAreUiccApplicationsEnabled = true;
+
+        /**
+         * the port index of the Uicc card.
+         */
+        private int mPortIndex = 0;
+
+        /**
+         * Subscription's preferred usage setting.
+         */
+        @UsageSetting
+        private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
+
+        /**
+         * Default constructor.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructor from {@link SubscriptionInfo}.
+         *
+         * @param info The subscription info.
+         */
+        public Builder(@NonNull SubscriptionInfo info) {
+            mId = info.mId;
+            mIccId = info.mIccId;
+            mSimSlotIndex = info.mSimSlotIndex;
+            mDisplayName = info.mDisplayName;
+            mCarrierName = info.mCarrierName;
+            mNameSource = info.mNameSource;
+            mIconTint = info.mIconTint;
+            mNumber = info.mNumber;
+            mDataRoaming = info.mDataRoaming;
+            mIconBitmap = info.mIconBitmap;
+            mMcc = info.mMcc;
+            mMnc = info.mMnc;
+            mEhplmns = info.mEhplmns;
+            mHplmns = info.mHplmns;
+            mCountryIso = info.mCountryIso;
+            mIsEmbedded = info.mIsEmbedded;
+            mNativeAccessRules = info.mNativeAccessRules;
+            mCardString = info.mCardString;
+            mCardId = info.mCardId;
+            mIsOpportunistic = info.mIsOpportunistic;
+            mGroupUuid = info.mGroupUuid;
+            mIsGroupDisabled = info.mIsGroupDisabled;
+            mCarrierId = info.mCarrierId;
+            mProfileClass = info.mProfileClass;
+            mType = info.mType;
+            mGroupOwner = info.mGroupOwner;
+            mCarrierConfigAccessRules = info.mCarrierConfigAccessRules;
+            mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled;
+            mPortIndex = info.mPortIndex;
+            mUsageSetting = info.mUsageSetting;
+        }
+
+        /**
+         * Set the subscription id.
+         *
+         * @param id The subscription id.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setId(int id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Set the ICCID of the SIM that is associated with this subscription.
+         *
+         * @param iccId The ICCID of the SIM that is associated with this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIccId(@Nullable String iccId) {
+            mIccId = TextUtils.emptyIfNull(iccId);
+            return this;
+        }
+
+        /**
+         * Set the SIM index of the slot that currently contains the subscription. Set to
+         * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive.
+         *
+         * @param simSlotIndex The SIM slot index.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setSimSlotIndex(int simSlotIndex) {
+            mSimSlotIndex = simSlotIndex;
+            return this;
+        }
+
+        /**
+         * The name displayed to the user that identifies this subscription. This name is used
+         * in Settings page and can be renamed by the user.
+         *
+         * @param displayName The display name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setDisplayName(@Nullable CharSequence displayName) {
+            mDisplayName = displayName == null ? "" : displayName;
+            return this;
+        }
+
+        /**
+         * The name displayed to the user that identifies subscription provider name. This name
+         * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+         *
+         * @param carrierName The carrier name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCarrierName(@Nullable CharSequence carrierName) {
+            mCarrierName = carrierName == null ? "" : carrierName;
+            return this;
+        }
+
+        /**
+         * Set the source of the carrier name.
+         *
+         * @param nameSource The source of the carrier name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNameSource(@SimDisplayNameSource int nameSource) {
+            mNameSource = nameSource;
+            return this;
+        }
+
+        /**
+         * Set the color to be used for tinting the icon when displaying to the user.
+         *
+         * @param iconTint The color to be used for tinting the icon when displaying to the user.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIconTint(int iconTint) {
+            mIconTint = iconTint;
+            return this;
+        }
+
+        /**
+         * Set the number presented to the user identify this subscription.
+         *
+         * @param number the number presented to the user identify this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNumber(@Nullable String number) {
+            mNumber = TextUtils.emptyIfNull(number);
+            return this;
+        }
+
+        /**
+         * Set whether user enables data roaming for this subscription or not.
+         *
+         * @param dataRoaming Data roaming mode. Either
+         * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+         * {@link SubscriptionManager#DATA_ROAMING_DISABLE}
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setDataRoaming(int dataRoaming) {
+            mDataRoaming = dataRoaming;
+            return this;
+        }
+
+        /**
+         * Set SIM icon bitmap cache.
+         *
+         * @param iconBitmap SIM icon bitmap cache.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIcon(@Nullable Bitmap iconBitmap) {
+            mIconBitmap = iconBitmap;
+            return this;
+        }
+
+        /**
+         * Set the mobile country code.
+         *
+         * @param mcc The mobile country code.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setMcc(@Nullable String mcc) {
+            mMcc = mcc;
+            return this;
+        }
+
+        /**
+         * Set the mobile network code.
+         *
+         * @param mnc Mobile network code.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setMnc(@Nullable String mnc) {
+            mMnc = mnc;
+            return this;
+        }
+
+        /**
+         * Set EHPLMNs associated with the subscription.
+         *
+         * @param ehplmns EHPLMNs associated with the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setEhplmns(@Nullable String[] ehplmns) {
+            mEhplmns = ehplmns == null ? new String[0] : ehplmns;
+            return this;
+        }
+
+        /**
+         * Set HPLMNs associated with the subscription.
+         *
+         * @param hplmns HPLMNs associated with the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setHplmns(@Nullable String[] hplmns) {
+            mHplmns = hplmns == null ? new String[0] : hplmns;
+            return this;
+        }
+
+        /**
+         * Set the ISO Country code for the subscription's provider.
+         *
+         * @param countryIso The ISO Country code for the subscription's provider.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCountryIso(@Nullable String countryIso) {
+            mCountryIso = TextUtils.emptyIfNull(countryIso);
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is from eSIM or not.
+         *
+         * @param isEmbedded {@code true} if the subscription is from eSIM.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setEmbedded(boolean isEmbedded) {
+            mIsEmbedded = isEmbedded;
+            return this;
+        }
+
+        /**
+         * Set the native access rules for this subscription, if it is embedded and defines any.
+         * This does not include access rules for non-embedded subscriptions.
+         *
+         * @param nativeAccessRules The native access rules for this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) {
+            mNativeAccessRules = nativeAccessRules;
+            return this;
+        }
+
+        /**
+         * Set the card string of the SIM card.
+         *
+         * @param cardString The card string of the SIM card.
+         * @return The builder.
+         *
+         * @see #getCardString()
+         */
+        @NonNull
+        public Builder setCardString(@Nullable String cardString) {
+            mCardString = TextUtils.emptyIfNull(cardString);
+            return this;
+        }
+
+        /**
+         * Set the card ID of the SIM card which contains the subscription.
+         *
+         * @param cardId The card ID of the SIM card which contains the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCardId(int cardId) {
+            mCardId = cardId;
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is opportunistic or not.
+         *
+         * @param isOpportunistic {@code true} if the subscription is opportunistic.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setOpportunistic(boolean isOpportunistic) {
+            mIsOpportunistic = isOpportunistic;
+            return this;
+        }
+
+        /**
+         * Set the group UUID of the subscription group.
+         *
+         * @param groupUuid The group UUID.
+         * @return The builder.
+         *
+         * @see #getGroupUuid()
+         */
+        @NonNull
+        public Builder setGroupUuid(@Nullable String groupUuid) {
+            mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
+            return this;
+        }
+
+        /**
+         * Whether group of the subscription is disabled. This is only useful if it's a grouped
+         * opportunistic subscription. In this case, if all primary (non-opportunistic)
+         * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+         * we should disable this opportunistic subscription.
+         *
+         * @param isGroupDisabled {@code true} if group of the subscription is disabled.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setGroupDisabled(boolean isGroupDisabled) {
+            mIsGroupDisabled = isGroupDisabled;
+            return this;
+        }
+
+        /**
+         * Set the subscription carrier id.
+         *
+         * @param carrierId The carrier id.
+         * @return The builder
+         *
+         * @see TelephonyManager#getSimCarrierId()
+         */
+        @NonNull
+        public Builder setCarrierId(int carrierId) {
+            mCarrierId = carrierId;
+            return this;
+        }
+
+        /**
+         * Set the profile class populated from the profile metadata if present.
+         *
+         * @param profileClass the profile class populated from the profile metadata if present.
+         * @return The builder
+         *
+         * @see #getProfileClass()
+         */
+        @NonNull
+        public Builder setProfileClass(@ProfileClass int profileClass) {
+            mProfileClass = profileClass;
+            return this;
+        }
+
+        /**
+         * Set the subscription type.
+         *
+         * @param type Subscription type.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setType(@SubscriptionType int type) {
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Set the owner package of group the subscription belongs to.
+         *
+         * @param groupOwner Owner package of group the subscription belongs to.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setGroupOwner(@Nullable String groupOwner) {
+            mGroupOwner = TextUtils.emptyIfNull(groupOwner);
+            return this;
+        }
+
+        /**
+         * Set the carrier certificates for this subscription that are saved in carrier configs.
+         * This does not include access rules from the Uicc, whether embedded or non-embedded.
+         *
+         * @param carrierConfigAccessRules The carrier certificates for this subscription
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCarrierConfigAccessRules(
+                @Nullable UiccAccessRule[] carrierConfigAccessRules) {
+            mCarrierConfigAccessRules = carrierConfigAccessRules;
+            return this;
+        }
+
+        /**
+         * Set whether Uicc applications are configured to enable or not.
+         *
+         * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to
+         * enable.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) {
+            mAreUiccApplicationsEnabled = uiccApplicationsEnabled;
+            return this;
+        }
+
+        /**
+         * Set the port index of the Uicc card.
+         *
+         * @param portIndex The port index of the Uicc card.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setPortIndex(int portIndex) {
+            mPortIndex = portIndex;
+            return this;
+        }
+
+        /**
+         * Set subscription's preferred usage setting.
+         *
+         * @param usageSetting Subscription's preferred usage setting.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setUsageSetting(@UsageSetting int usageSetting) {
+            mUsageSetting = usageSetting;
+            return this;
+        }
+
+        /**
+         * Build the {@link SubscriptionInfo}.
+         *
+         * @return The {@link SubscriptionInfo} instance.
+         */
+        public SubscriptionInfo build() {
+            return new SubscriptionInfo(this);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4fb6587..5a2b4fc 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3099,7 +3099,7 @@
     @SystemApi
     public boolean canManageSubscription(@NonNull SubscriptionInfo info,
             @NonNull String packageName) {
-        if (info == null || info.getAllAccessRules() == null || packageName == null) {
+        if (info == null || info.getAccessRules() == null || packageName == null) {
             return false;
         }
         PackageManager packageManager = mContext.getPackageManager();
@@ -3111,7 +3111,7 @@
             logd("Unknown package: " + packageName);
             return false;
         }
-        for (UiccAccessRule rule : info.getAllAccessRules()) {
+        for (UiccAccessRule rule : info.getAccessRules()) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 return true;
@@ -3456,10 +3456,20 @@
      * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
      * See {@link #createSubscriptionGroup(List)} for more details.
      *
-     * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE}
-     * permission or had carrier privilege permission on the subscription.
+     * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
+     * or carrier privilege permission on the subscription.
      * {@link TelephonyManager#hasCarrierPrivileges()}
      *
+     * <p>Starting with API level 33, the caller also needs permission to access device identifiers
+     * to get the list of subscriptions associated with a group UUID.
+     * This method can be invoked if one of the following requirements is met:
+     * <ul>
+     *     <li>If the app has carrier privilege permission.
+     *     {@link TelephonyManager#hasCarrierPrivileges()}
+     *     <li>If the app has {@link android.Manifest.permission#READ_PHONE_STATE} permission and
+     *     access to device identifiers.
+     * </ul>
+     *
      * @throws IllegalStateException if Telephony service is in bad state.
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b4244dd..12b4114 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -191,6 +191,9 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
 
+    // Null IMEI anomaly uuid
+    private static final UUID IMEI_ANOMALY_UUID = UUID.fromString(
+            "83905f14-6455-450c-be29-8206f0427fe9");
     /**
      * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
      * into the ResultReceiver Bundle.
@@ -2181,7 +2184,11 @@
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     public String getImei() {
-        return getImei(getSlotIndex());
+        String imei = getImei(getSlotIndex());
+        if (imei == null) {
+            AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null.");
+        }
+        return imei;
     }
 
     /**
@@ -2224,7 +2231,10 @@
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     public String getImei(int slotIndex) {
         ITelephony telephony = getITelephony();
-        if (telephony == null) return null;
+        if (telephony == null) {
+            AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null");
+            return null;
+        }
 
         try {
             return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag());
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 54662ef..725963b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -214,5 +214,34 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <service
+            android:name=".AssistantInteractionSessionService"
+            android:exported="true"
+            android:permission="android.permission.BIND_VOICE_INTERACTION" />
+        <service
+            android:name=".AssistantRecognitionService"
+            android:exported="true"
+            android:label="Test Voice Interaction Service">
+            <intent-filter>
+                <action android:name="android.speech.RecognitionService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data
+                android:name="android.speech"
+                android:resource="@xml/recognition_service" />
+        </service>
+        <service
+            android:name=".AssistantInteractionService"
+            android:exported="true"
+            android:label="Test Voice Interaction Service"
+            android:permission="android.permission.BIND_VOICE_INTERACTION">
+            <intent-filter>
+                <action android:name="android.service.voice.VoiceInteractionService" />
+            </intent-filter>
+            <meta-data
+                android:name="android.voice_interaction"
+                android:resource="@xml/interaction_service" />
+        </service>
     </application>
+    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
 </manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml
new file mode 100644
index 0000000..eb7f307
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:id="@+id/vis_frame"
+        android:layout_width="match_parent"
+        android:layout_height="300dp"
+        android:layout_gravity="bottom"
+        android:background="#37474F"/>
+</FrameLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml
new file mode 100644
index 0000000..2e661fb
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:recognitionService="com.android.server.wm.flicker.testapp.AssistantRecognitionService"
+    android:sessionService="com.android.server.wm.flicker.testapp.AssistantInteractionSessionService"
+    android:supportsAssist="true" />
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml
new file mode 100644
index 0000000..2e12498
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml
@@ -0,0 +1,17 @@
+<!--
+  ~ 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.
+  -->
+
+<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java
new file mode 100644
index 0000000..d60143c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.service.voice.VoiceInteractionService;
+
+public class AssistantInteractionService extends VoiceInteractionService {
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java
new file mode 100644
index 0000000..d2c9b37
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.view.View;
+import android.view.Window;
+
+public class AssistantInteractionSession extends VoiceInteractionSession {
+
+    public AssistantInteractionSession(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onCreate() {
+        Window window = getWindow().getWindow();
+        if (window != null) {
+            window.getDecorView().setBackgroundColor(Color.TRANSPARENT);
+
+        }
+        View rootView = getLayoutInflater().inflate(R.layout.assistant_session, null);
+        setContentView(rootView);
+        setUiEnabled(false);
+    }
+
+    @Override
+    public void onShow(Bundle args, int showFlags) {
+        setUiEnabled(true);
+    }
+
+    @Override
+    public void onHide() {
+        setUiEnabled(false);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java
new file mode 100644
index 0000000..4d6125c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class AssistantInteractionSessionService extends VoiceInteractionSessionService {
+    @Override
+    public VoiceInteractionSession onNewSession(Bundle args) {
+        return new AssistantInteractionSession(this);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java
new file mode 100644
index 0000000..68aae45
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.content.Intent;
+import android.speech.RecognitionService;
+
+public class AssistantRecognitionService extends RecognitionService {
+    @Override
+    protected void onStartListening(Intent recognizerIntent, Callback listener) {
+
+    }
+
+    @Override
+    protected void onCancel(Callback listener) {
+
+    }
+
+    @Override
+    protected void onStopListening(Callback listener) {
+
+    }
+}
diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp
index eeb9e3c..f8629f9 100644
--- a/tools/fonts/Android.bp
+++ b/tools/fonts/Android.bp
@@ -24,12 +24,7 @@
 python_defaults {
     name: "fonts_python_defaults",
     version: {
-        py2: {
-            enabled: false,
-            embedded_launcher: false,
-        },
         py3: {
-            enabled: true,
             embedded_launcher: true,
         },
     },